Visual C++

Im Blickpunkt: Neue C++- und MFC-Features in Visual Studio 2010

Sumit Kumar

Visual Studio 2010 bietet C++-Entwicklern wesentliche Vorteile. Von der Möglichkeit, die neuen Features von Windows 7 einzusetzen, bis hin zu den verbesserten Produktivitätsfeatures zum Arbeiten mit großen Codebasen ist wohl für jeden C++-Entwickler etwas dabei.

In diesem Artikel wird erläutert, wie Microsoft einige der großen Probleme gelöst hat, denen C++-Entwickler häufig gegenüberstehen. Insbesondere ermöglicht Visual Studio 2010 ein moderneres Programmiermodell, indem zentrale Sprachfeatures des nächsten C++0x-Standards integriert wurden und die Standardbibliothek entsprechend überholt wurde. Durch neue Bibliotheken und Tools für die Parallelprogrammierung wird die Erstellung paralleler Programme vereinfacht. Dank IntelliSense und für große Codebasen skalierbarer Features zur Codeanalyse wird eine verbesserte Gesamtleistung und Entwicklerproduktivität erzielt. Darüber hinaus profitieren Sie von der verbesserten Leistung von Bibliotheken und anderen Features, von der Entwurfs-, Build- und Kompilierungszeit bis zur Link-Zeit.

In Visual Studio 2010 wird das Buildsystem zu MSBuild migriert, um seine Anpassung zu erleichtern und die systemeigene Festlegung von Zielversionen zu unterstützen. Verbesserungen in der MFC-Bibliothek machen zudem die Funktionsstärke neuer Windows 7-APIs nutzbar, sodass Sie hervorragende Windows 7-Anwendungen schreiben können.

Werfen wir einen Blick auf die Verbesserungen bezüglich C++ in Visual Studio 2010.

Zentrale Sprachfeatures von C++0x

Der nächste C++-Standard nähert sich langsam der Verabschiedung. Um Ihnen den Einstieg in die Erweiterungen von C++0x zu erleichtern, bietet der Visual C++-Compiler in Visual Studio 2010 sechs zentrale C++0x-Sprachfeatures: Lambda-Ausdrücke, das <strong>auto</strong>-Schlüsselwort, R-Wert-Verweise, <strong>static_assert</strong>, <strong>nullptr</strong> und <strong>decltype</strong>.

In Lambda-Ausdrücken werden unbenannte Funktionsobjekte implizit definiert und konstruiert. Lambdas bieten eine einfache natürliche Syntax zur Definition von Funktionsobjekten an der Stelle ihrer Verwendung, ohne zusätzlichen Leistungsaufwand.

Funktionsobjekte sind eine sehr leistungsstarke Möglichkeit zur Anpassung des Verhaltens von STL-Algorithmen (Standard Template Library, Standardvorlagenbibliothek) und können sowohl Code als auch Daten kapseln (im Gegensatz zu einfachen Funktionen). Allerdings sind Funktionsobjekte nicht einfach zu definieren, da vollständige Klassen geschrieben werden müssen. Zudem werden sie nicht an der Stelle des Quellcodes definiert, an der sie verwendet werden sollen. Diese Nichtlokalität erschwert ihre Verwendung. Mithilfe von Bibliotheken wurde versucht, einige Probleme der Ausführlichkeit und Nichtlokalität zu verringern. Allerdings wird dadurch die Syntax kompliziert, und unangenehme Compilerfehler treten auf. Die Verwendung von Funktionsobjekten aus Bibliotheken ist also weniger effizient, da die als Datenmember definierten Funktionsobjekte nicht "inline" sind.

Lambda-Ausdrücke lösen diese Probleme. Der folgende Codeausschnitt zeigt einen Lambda-Ausdruck, mit dem in einem Programm die ganzen Zahlen zwischen den Variablen x und y aus einem Vektor von ganzen Zahlen entfernt werden.

v.erase(remove_if(v.begin(),
   v.end(), [x, y](int n) { 
   return x < n && n < y; }),
   v.end());

Die zweite Zeile enthält den Lambda-Ausdruck. Eckige Klammern, die als Lambda-Introducer bezeichnet werden, zeigen die Definition eines Lambda-Ausdrucks an. Dieses Lambda akzeptiert die ganze Zahl n als Parameter, und das Lambda-generierte Funktionsobjekt hat die Datenmember x und y. Vergleichen Sie dies mit einem äquivalenten handcodierten Funktionsobjekt, um sich zu überzeugen, wie praktisch und zeitsparend Lambdas sind:

class LambdaFunctor {
public:
  LambdaFunctor(int a, int b) : m_a(a), m_b(b) { }
  bool operator()(int n) const {
    return m_a < n && n < m_b; }
private:
  int m_a;
  int m_b;
};
v.erase(remove_if(v.begin(), v.end(),
  LambdaFunctor(x, y)), v.end());

Das auto-Schlüsselwort war bereits in C++ verfügbar, wurde jedoch selten verwendet, da es keinen zusätzlichen Nutzen bot. In C++0x wird das Schlüsselwort verwendet, um den Typ einer Variablen automatisch aus ihrem Initialisierer zu bestimmen. Durch Auto wird die Ausführlichkeit reduziert und wichtiger Code hervorgehoben. Es vermeidet falsche Typzuordnungen und Kürzungsfehler. Zudem fördert es die Erstellung von generischem Code, da in Vorlagen weniger Wert auf die Typen von Zwischenausdrücken gelegt werden muss und undokumentierte Typen wie Lambdas behandelt werden. Der folgende Code ist ein Beispiel dafür, wie auto die Eingabe des Vorlagentyps in der For-Schleife über einen Vektor erspart:

vector<int> v;
for (auto i = v.begin(); i != v.end(); ++i) {
// code 
}

R-Wert-Verweise stellen einen in C++0x neu eingeführten Verweistyp dar, der zur Lösung des Problems unnötiger Kopiervorgänge beiträgt und Perfect Forwarding ermöglicht. Ist die rechte Seite einer Zuweisung ein R-Wert, kann das linksseitige Objekt statt einer separaten Zuordnung Ressourcen vom rechtsseitigen Objekt übernehmen. So wird eine move-Semantik ermöglicht.

Perfect Forwarding ermöglicht das Schreiben einer einzelnen Funktionsvorlage, die n beliebige Argumente akzeptiert und diese transparent an eine andere beliebige Funktion übergibt. Die Art des Arguments (veränderbar, konstant, L-Wert oder R-Wert) bleibt dabei erhalten.

template <typename T1, typename T2> void functionA(T1&& t1, T2&& t2) {
  functionB(std::forward<T1>(t1), std::forward<T2>(t2));
}

Eine ausführliche Erläuterung von R-Wert-Verweisen würde den Rahmen dieses Artikels sprengen. Weitere Informationen finden Sie in der MSDN-Dokumentation unter msdn.microsoft.com/library/dd293668(VS.100).

Static_assert ermöglicht das Testen von Zuweisungen zur Kompilierzeit statt zur Laufzeit. Es bietet die Möglichkeit, Compilerfehler mit leicht lesbaren benutzerdefinierten Fehlermeldungen auszulösen. Static_assert ist besonders nützlich für die Validierung von Vorlagenparametern. Bei der Kompilierung des folgenden Codes wird beispielsweise folgender Fehler erzeugt: "error C2338: custom assert: n should be less than 5":

template <int n> struct StructA {
  static_assert(n < 5, "custom assert: n should be less than 5");
};

int _tmain(int argc, _TCHAR* argv[]) {
  StructA<4> s1;
  StructA<6> s2;
  return 0;
}

Nullptr versieht Nullzeiger mit Typensicherheit und ist eng verwandt mit R-Wert-Verweisen. Im Allgemeinen werden das Makro NULL (definiert als 0) und das Literal 0 als Nullzeiger verwendet. Bisher war dies kein Problem, in C++0x sind diese jedoch aufgrund möglicher Probleme beim Perfect Forwarding nicht uneingeschränkt funktionsfähig. Daher wurde das nullptr-Schlüsselwort eingeführt, vor allem, um unverständliche Fehler bei Perfect Forwarding-Funktionen zu vermeiden.

Nullptr ist eine Konstante vom Typ nullptr_t, der in jeden beliebigen Zeigertyp konvertierbar ist, jedoch nicht in andere Typen wie int oder char. Außer in Perfect Forwarding-Funktionen kann nullptr auch an allen anderen Stellen verwendet werden, an denen das Makro NULL als Nullzeiger verwendet wurde.

Aber Vorsicht: NULL wird weiterhin vom Compiler unterstützt und wurde noch nicht durch nullptr ersetzt. Dadurch soll vor allem vermieden werden, dass vorhandener Code aufgrund der weit verbreiteten und oft unangemessenen Verwendung von NULL unbrauchbar wird. In Zukunft sollte jedoch nullptr anstelle der bisherigen Verwendung von NULL eingesetzt werden, und NULL sollte lediglich zur Unterstützung der Abwärtskompatibilität behandelt werden.

Schließlich ermöglicht es decltype dem Compiler, den Rückgabetyp einer Funktion basierend auf einem beliebigen Ausdruck abzuleiten, und macht das Perfect Forwarding generischer. In früheren Versionen konnte aus zwei beliebigen Typen T1 und T2 nicht der Typ eines Ausdrucks abgeleitet werden, der diese verwendet. Mit dem Feature decltype können Sie einen Ausdruck mit Vorlagenargumenten angeben. Beispielsweise ist sum<T1, T2>() vom Typ T1+T2.

Verbesserungen der Standardbibliothek

Wesentliche Teile der C++-Standardbibliothek wurden neu geschrieben, um die neuen C++0x-Sprachfeatures zu nutzen und die Leistung zu erhöhen. Zudem wurden zahlreiche neue Algorithmen eingeführt.

In der Standardbibliothek werden R-Wert-Verweise zur Verbesserung der Leistung genutzt. Typen wie Vektor und Liste verfügen nun über eigene move-Konstruktoren und move-Zuweisungsoperatoren. Bei Vektorneuzuordnungen wird die move-Semantik genutzt, indem move-Konstruktoren erfasst werden. Wenn Ihre Typen move-Konstruktoren und move-Zuweisungsoperatoren aufweisen, werden diese also automatisch durch die Bibliothek erfasst.

Mithilfe der neuen C++0x-Funktionsvorlage make_shared<T> können Sie jetzt beim Erstellen eines Objekts einen Shared Pointer auf das Objekt erstellen:

auto sp = 
  make_shared<map<string,vector>>
  (args);

In Visual Studio 2008 ist folgender Code erforderlich, um dieselbe Funktionalität zu erzielen:

shared_ptr<map<string,vector>> 
  sp(new map<string,vector>(args));

Die Verwendung von make_shared<T> ist viel bequemer (Sie müssen den Typnamen nicht so oft schreiben), robuster (der übliche Verlust durch unbenannte Shared Pointer wird vermieden, da Zeiger und Objekt gleichzeitig erstellt werden) und effizienter (statt zwei dynamischen Speicherreservierungen wird nur eine ausgeführt). 

Die Bibliothek enthält nun einen neuen, sichereren Typ intelligenter Zeiger, unique_ptr (der durch R-Wert-Verweise ermöglicht wurde). Infolgedessen wurde auto_ptr als veraltet erklärt. Die Fallstricke von auto_ptr werden bei unique_ptr vermieden, da es verschiebbar, aber nicht kopierbar ist. Hiermit können Sie die Semantik des strikten Besitzes implementieren, ohne die Sicherheit zu beeinträchtigen. Der Zeigertyp ist auch mit Visual C++ 2010-Containern verwendbar, die R-Wert-Verweise unterstützen.

Container verfügen nun über die neuen Memberfunktionen cbegin und cend, die die Verwendung von const_iterator zur Inspektion unabhängig vom Typ des Containers ermöglichen:

vector<int> v;
 
for (auto i = v.cbegin(); i != v.cend(); ++i) {
  // i is vector<int>::const_iterator
}

In Visual Studio 2010 wurden die meisten in verschiedenen C++0x-Dokumenten vorgeschlagenen Algorithmen in die Standardbibliothek aufgenommen. Eine Teilmenge der Dinkumware-Bibliothek ist jetzt in der Standardbibliothek verfügbar. Somit können Sie nun problemlos Konvertierungen wie beispielsweise von UTF-8 nach UTF-16 ausführen. Die Standardbibliothek ermöglicht die Ausnahmepropagierung über exception_ptr. Am Header <random> wurden zahlreiche Updates vorgenommen. In dieser Version gibt es eine einzeln verknüpfte Liste namens forward_list. Die Bibliothek enthält einen Header <system_error> für bessere Diagnosemöglichkeiten. Viele TR1-Features, die in der früheren Version im Namespace std::tr1 vorhanden waren (wie shared_ptr und regex) sind jetzt in der Standardbibliothek unter dem Namespace std enthalten.

Verbesserungen der Parallelprogrammierung

Visual Studio 2010 enthält neu die Parallel Computing Platform, mit der Sie schnell hochleistungsfähigen parallelen Code entwickeln und dabei komplizierte Parallelitätsfehler vermeiden können. Auf diese Weise können Sie einige der üblichen Probleme bei der Parallelität umgehen.

Die Parallel Computing Platform besteht im Wesentlichen aus vier Teilen: Concurrency Runtime (ConcRT), Parallel Patterns Library (PPL), Asynchronous Agents Library sowie parallelen Debugging- und Profilerstellungstools.

ConcRT ist die unterste Softwareschicht, die mit dem Betriebssystem kommuniziert und zwischen mehreren gleichzeitigen, um Ressourcen konkurrierenden Komponenten vermittelt. Da es sich um einen Benutzermodusprozess handelt, kann er Ressourcen freigeben, wenn seine kooperativen Blockierungsmechanismen verwendet werden. ConcRT berücksichtigt die Lokalität und vermeidet das Wechseln von Tasks zwischen verschiedenen Prozessoren. Da zudem User Mode Scheduling (UMS) von Windows 7 eingesetzt wird, kann die Leistung auch ohne den kooperativen Blockierungsmechanismus gesteigert werden.

PPL stellt die Muster zum Schreiben von parallelem Code bereit. Wenn eine Berechnung in Teilberechnungen zerlegt werden kann, die durch Funktionen oder Funktionsobjekte dargestellt werden können, kann jede dieser Teilberechnungen durch einen Task dargestellt werden. Das Taskkonzept liegt wesentlich näher an der Problemdomäne als das Konzept der Threads, bei dem Sie über Hardware, BS, kritische Abschnitte usw. nachdenken müssen. Ein Task kann gleichzeitig mit anderen Tasks und unabhängig von diesen ausgeführt werden. Das Sortieren von zwei Hälften eines Arrays kann beispielsweise von zwei verschiedenen Tasks gleichzeitig ausgeführt werden.

PPL enthält parallele Klassen (task_handle, task_group und structured_task_group), parallele Algorithmen (parallel_invoke, parallel_for und parallel_for_each), parallele Container (combinable, concurrent_queue und concurrent_vector) sowie ConcRT-kompatible Synchronisierungsprimitive (critical_section, event und reader_writer_lock), die sämtlich Tasks als Konzept erster Klasse behandeln. Alle Komponenten von PPL befinden sich im Namespace Concurrency.

Mithilfe von Taskgruppen können Sie einen Satz von Tasks ausführen und warten, bis alle abgeschlossen sind. So können etwa in dem Sortierungsbeispiel die Tasks für die beiden Hälften des Arrays eine Taskgruppe bilden. Es ist sichergestellt, dass die beiden Tasks nach dem Aufruf der wait-Memberfunktion abgeschlossen sind, wie das Codebeispiel einer rekursiven Quicksort-Implementierung mit parallelen Tasks und Lambdas zeigt:

void quicksort(vector<int>::iterator first,
vector<int>::iterator last) {
  if (last - first < 2) { return; }
  int pivot = *first;
  auto mid1 = partition(first, last, [=](int elem) { 
    return elem < pivot; });
  auto mid2 = partition( mid1, last, [=](int elem) { 
    return elem == pivot; });
  task_group g;
  g.run([=] { quicksort(first, mid1); });
  g.run([=] { quicksort(mid2, last); });
  g.wait();
}

Dies kann durch die Verwendung einer strukturierten Taskgruppe weiter verbessert werden, die durch den parallel_invoke-Algorithmus aktiviert wird. Dieser nimmt 2 bis 10 Funktionsobjekte an, führt alle parallel mit der von ConcRT bereitgestellten Anzahl von Kernen aus und wartet auf deren Abschluss:

parallel_invoke(
  [=] { quicksort(first, mid1); },
  [=] { quicksort(mid2, last); } );

parallel_invoke(
  [=] { quicksort(first, mid1); },
  [=] { quicksort(mid2, last); } );

Von jedem dieser Tasks können mehrere Subtasks erstellt werden. Die Zuordnung zwischen Tasks und Ausführungsthreads (und die Sicherstellung, dass alle Kerne optimal ausgelastet werden) erfolgt durch ConcRT. Somit hilft das Zerlegen einer Berechnung in möglichst viele Tasks dabei, alle verfügbaren Kerne zu nutzen.

Ein weiterer nützlicher paralleler Algorithmus ist parallel_for, mit dem Indizes auf parallele Art und Weise durchlaufen werden können:

parallel_for(first, last, functor);
parallel_for(first, last, step, functor);

Hiermit werden gleichzeitig Funktionsobjekte mit jedem Index aufgerufen, beginnend mit dem ersten und endend mit dem letzten.

Mit der Asynchronous Agents Library steht Ihnen ein datenflussbasiertes Programmiermodell zur Verfügung, bei dem Berechnungen von der Verfügbarkeit der erforderlichen Daten abhängig sind. Die Bibliothek basiert auf den Konzepten der Agents, Nachrichtenblöcke und Nachrichtenaustausch-Funktionen (Message Passing). Ein Agent ist eine Komponente einer Anwendung, die bestimmte Berechnungen ausführt und asynchron mit anderen Agents kommuniziert, um ein umfassenderes Berechnungsproblem zu lösen. Die Kommunikation zwischen Agents erfolgt über Nachrichtenaustausch-Funktionen und Nachrichtenblöcke).

Agents haben einen überwachbaren Lebenszyklus, der verschiedene Phasen durchläuft. Sie sollten nicht für den feinkörnigen Parallelismus verwendet werden, der mit PPL-Tasks erzielt wird. Agents bauen auf den Planungs- und Ressourcenverwaltungskomponenten von ConcRT auf und tragen zur Vermeidung der Probleme bei, die bei der Verwendung von gemeinsam genutzten Speicherbereichen in gleichzeitigen Anwendungen auftreten.

Sie müssen keine zusätzlichen Komponenten einbinden oder erneut verteilen, um diese Muster nutzen zu können. ConcRT, PPL und Asynchronous Agents Library wurden zusammen mit der Standardbibliothek in msvcr100.dll, msvcp100.dll und libcmt.lib/libcpmt.lib implementiert. PPL und die Asynchronous Agents Library sind im Wesentlichen Header-only-Implementierungen.

Der Visual Studio-Debugger ist nun mit ConcRT kompatibel und erleichtert das Debuggen von Parallelitätsproblemen – anders als in Visual Studio 2008, wo Parallelitätskonzepte höherer Ebenen unbekannt waren. Visual Studio 2010 verfügt über einen Parallelitäts-Profiler, mit dem Sie das Verhalten paralleler Anwendungen visualisieren können. Der Debugger weist neue Fenster auf, die den Status aller Tasks (Aufgaben) in einer Anwendung und deren Aufruflisten visualisieren. Abbildung 1 zeigt die Fenster Parallele Aufgaben und Parallele Stapel.

Abbildung 1: Debug-Fenster 'Parallele Aufgaben' und 'Parallele Stapel'
Abbildung 1 Debug-Fenster "Parallele Aufgaben" und "Parallele Stapel"

IntelliSense und Entwurfszeit-Produktivität

In Visual Studio 2010 ist eine völlig neue IntelliSense- und Browsing-Infrastruktur enthalten. Außer Hilfe zur Skalierung und Reaktionsfähigkeit bei Projekten mit großen Codebasen haben die Verbesserungen der Infrastruktur einige Entwurfszeit-Produktivitätsfeatures ermöglicht.

IntelliSense-Features wie Live-Fehlerberichterstattung und QuickInfo basieren auf einem neuen Compiler-Front-End, das die gesamte Übersetzungseinheit analysiert, um reichhaltige und genaue Informationen zur Codesemantik zu liefern, auch während die Codedateien geändert werden.

Alle Features zum Durchsuchen von Code, wie Klassenansicht und Klassenhierarchie, verwenden nun die Quellcodeinformationen aus einer SQL-Datenbank, die Indizierung ermöglicht und einen festen Speicherbedarf aufweist. Im Gegensatz zu früheren Versionen ist die Visual Studio 2010-IDE stets reaktionsfähig, und Sie müssen nicht mehr warten, bis Kompilierungseinheiten nach Änderungen in einer Headerdatei erneut analysiert wurden.

Die Live-Fehlerberichterstattung von IntelliSense (die gewohnten roten Wellenlinien) zeigt beim Durchsuchen und Bearbeiten von Code dem Compiler entsprechende syntaktische und semantische Fehler an. Wenn Sie die Maus über den Fehler bewegen, wird die Fehlermeldung angezeigt (siehe Abbildung 2). Das Fehlerlistenfenster zeigt ebenfalls den Fehler aus der aktuell angezeigten Datei sowie die IntelliSense-Fehler an anderen Stellen der Kompilierungseinheit. All diese Informationen sind verfügbar, ohne dass Sie einen Build ausführen.

Abbildung 2: Live-Fehlerberichterstattung mit IntelliSense-Fehlern
Abbildung 2 Live-Fehlerberichterstattung mit IntelliSense-Fehlern

Darüber hinaus wird bei der Eingabe von #include eine Liste relevanter Includedateien in einer Dropdownliste angezeigt, und die Liste wird während der Eingabe weiter verfeinert.

Das neue Feature "Navigieren zu" (Bearbeiten | Navigieren zu oder STRG+Komma) verhilft Ihnen zu höherer Produktivität bei der Datei- oder Symbolsuche. Dieses Feature bietet auf Teilzeichenfolgen der Eingabe basierende Suchergebnisse in Echtzeit, indem Eingabezeichenfolgen mit Symbolen und Dateien in beliebigen Projekten verglichen werden (siehe Abbildung 3). Das Feature ist auch für C#- und Visual Basic-Dateien verwendbar und ist erweiterbar.

Abbildung 3: Verwenden des Features 'Navigieren zu'
Abbildung 3 Verwenden des Features "Navigieren zu"

In der Aufrufhierarchie (aufgerufen mit STRG+K, STRG+T oder über das Kontextmenü) können Sie zu allen Funktionen navigieren, die von einer bestimmten Funktion aufgerufen werden, und zu allen Funktionen, die eine bestimmte Funktion aufrufen. Hierbei handelt es sich um eine verbesserte Version des Features Aufrufbrowser aus früheren Versionen von Visual Studio. Das Aufrufhierarchie-Fenster ist wesentlich besser organisiert und enthält ein- und ausgehende Aufrufstrukturen für alle Funktionen, die in demselben Fenster erscheinen.

Beachten Sie, dass alle Features zum Durchsuchen von Code sowohl für reines C++ als auch C++/CLI verfügbar sind, IntelliSense-bezogene Features wie Live-Fehlerberichterstattung und QuickInfo jedoch nicht für C++/CLI in der endgültigen Version von Visual Studio 2010 zur Verfügung stehen. 

Andere wesentliche Editorfeatures wurden in dieser Version ebenfalls verbessert. Beispielsweise ist das beliebte Feature zum Suchen aller Verweise auf bestimmte Codeelemente (Klassen, Klassenmember, Funktionen usw.) in der gesamten Projektmappe nun flexibler. Suchergebnisse können mit der Option Ergebnisse auflösen aus dem Kontextmenü weiter verfeinert werden.

Semantische Informationen für inaktiven Code durch die farbliche Kennzeichnung bleiben erhalten (statt grau gefärbt zu werden). Abbildung 4 zeigt, wie der inaktive Code abgeblendet wird, die semantischen Informationen jedoch weiterhin durch verschiedene Farben übermittelt werden.

Abbildung 4: Farbliche Kennzeichnung in inaktiven Codeblöcken bleibt erhalten
Abbildung 4 Die farbliche Kennzeichnung in inaktiven Codeblöcken bleibt erhalten

Neben den beschriebenen Features wurde die allgemeine Benutzerfreundlichkeit des Editors in Visual Studio 2010 verbessert. Die neue auf Windows Presentation Foundation (WPF) basierende IDE wurde neu gestaltet, um Übersichtlichkeit und Lesbarkeit zu verbessern. Dokumentfenster wie der Code-Editor und die Entwurfsansicht können nun aus dem IDE-Hauptfenster gezogen und auf mehreren Monitoren angezeigt werden. Das Zoomen im Code-Editor-Fenster erfolgt einfacher mit der STRG-TASTE und dem Mausrad. Die IDE verfügt zudem über verbesserte Unterstützung für Erweiterungen.

Build- und Projektsysteme

Visual Studio 2010 zeichnet sich durch grundlegende Verbesserungen im Buildsystem und im Projektsystem für C++-Projekte aus.

Die wichtigste Änderung besteht darin, dass MSBuild nun zum Erstellen von C++-Projekten verwendet wird. MSBuild ist ein erweiterbares, XML-basiertes Buildorchestrierungsmodul, das in früheren Versionen von Visual Studio für C#- und Visual Basic-Projekte verwendet wurde. MSBuild stellt nun das gemeinsame Microsoft-Buildsystem für alle Sprachen dar. Es kann im Build-Labor und auf einzelnen Entwicklercomputern verwendet werden.

C++-Buildprozesse werden nun in Form von MSBuild-Ziel- und Taskdateien definiert und sind stärker anpassbar, besser steuerbar und transparenter.

Der C++-Projekttyp besitzt eine neue Erweiterung: .VCXPROJ. Alte VCPROJ-Dateien und ‑Projektmappen werden automatisch von Visual Studio auf das neue Format aktualisiert. Einzelne Projekte können auch mit dem Befehlszeilentool vcupgrade.exe aktualisiert werden.

Bisher konnten Sie nur das Toolset (Compiler, Bibliotheken usw.) verwenden, das mit Ihrer aktuellen Version von Visual Studio bereitgestellt wurde. Erst als Sie zur Migration zum neuen Toolset bereit waren, konnten Sie die neue IDE verwenden. In Visual Studio 2010 wurde das Problem dadurch gelöst, dass Sie den Buildprozess für mehrere Toolsetversionen ausführen können. Sie können beispielsweise während der Arbeit in Visual Studio 2010 den Compiler und die Bibliotheken von Visual C++ 9.0 verwenden. Abbildung 5 zeigt die systemeigenen Einstellungen für die Festlegung von Zielversionen auf der Eigenschaftenseite.

Abbildung 5: Festlegen des Ziel-Plattformtoolsets
Abbildung 5 Festlegen des Ziel-Plattformtoolsets

Durch die Verwendung von MSBuild wird das C++-Buildsystem wesentlich besser erweiterbar. Wenn das Standardbuildsystem Ihren Anforderungen nicht genügt, können Sie es durch Hinzufügen eines eigenen Tools oder eines anderen Buildschritts erweitern. MSBuild verwendet Tasks als wiederverwendbare Einheiten von ausführbarem Code zur Ausführung der Buildvorgänge. Sie können eigene Tasks erstellen und das Buildsystem erweitern, indem Sie sie in einer XML-Datei definieren. MSBuild generiert die Tasks dynamisch aus diesen XML-Dateien.

Vorhandene Plattformen und Toolsets können durch Hinzufügen von PROPS- und TARGETS-Dateien für zusätzliche Schritte in den Ordnern ImportBefore und ImportAfter erweitert werden. Dies ist besonders für Anbieter von Bibliotheken und Tools nützlich, die die vorhandenen Buildsysteme erweitern möchten. Sie können auch Ihr eigenes Plattformtoolset erstellen. Darüber hinaus bietet MSBuild bessere Diagnoseinformationen, um das Debuggen von Buildproblemen zu erleichtern. Dadurch werden auch inkrementelle Builds zuverlässiger. Des Weiteren können Sie Buildsysteme erstellen, die enger an die Quellcodeverwaltung und das Build-Labor gekoppelt sind und weniger von der Konfiguration des Entwicklercomputers abhängig sind.

Das auf dem Buildsystem aufbauende Projektsystem profitiert ebenfalls von der Flexibilität und Erweiterbarkeit von MSBuild. Das Projektsystem kennt die MSBuild-Prozesse und ermöglicht die transparente Anzeige der von MSBuild verfügbar gemachten Informationen in Visual Studio.

Anpassungen sind sichtbar und können über die Eigenschaftenseiten konfiguriert werden. Sie können das Projektsystem für die Verwendung Ihrer eigenen Plattform (wie der vorhandenen x86- oder x64-Plattformen) oder Ihres eigenen Debuggers konfigurieren. Die Eigenschaftenseiten bieten Ihnen die Möglichkeit, Komponenten zu erstellen und zu integrieren, die den Wert von kontextabhängigen Eigenschaften dynamisch aktualisieren. Das Projektsystem von Visual Studio 2010 ermöglicht sogar das Schreiben einer eigenen benutzerdefinierten Benutzeroberfläche zum Lesen und Schreiben von Eigenschaften, statt Eigenschaftenseiten zu verwenden.

Schnellere Kompilierung und gesteigerte Leistung

Neben der beschriebenen verbesserten Handhabung zur Entwurfszeit wurden in Visual Studio 2010 auch die Kompilierungsgeschwindigkeit, die Qualität und die Leistung von mit dem Visual C++-Compiler erstellten Anwendungen verbessert, indem mehrere Codegenerierungsverbesserungen am Compiler-Back-End vorgenommen wurden.

Die Leistung bestimmter Anwendungen ist vom Arbeitssatz abhängig. Die Codegröße für die x64-Architektur wurde durch Optimierungen in dieser Version um 3 bis 10 Prozent reduziert, was eine Leistungssteigerung für solche Anwendungen bewirkt.

Die SIMD-Codegenerierung (Single Instruction Multiple Data), die für Entwickler von Spielen, Audio, Video und Grafiken wichtig ist, wurde bezüglich Leistung und Codequalität optimiert. Zu den Verbesserungen gehören das Beseitigen falscher Abhängigkeiten, die Vektorisierung konstanter Vektorinitialisierungen und die bessere Zuordnung von XMM-Registern zur Entfernung redundanter load-, store- und move-Vorgänge. Zudem wurde die intrinsische Familie __mm_set_**, __mm_setr_** und __mm_set1_** optimiert.

Für eine verbesserte Leistung sollten Anwendungen mit Link-Zeitcodegenerierung (LTCG) und profilgesteuerter Optimierung (PGO) erstellt werden.

Die Kompilierungsgeschwindigkeit auf x64-Plattformen wurde durch Optimierungen in der x64-Codegenerierung verbessert. Die Kompilierung mit LTCG, empfohlen für eine bessere Optimierung, nimmt in der Regel mehr Zeit in Anspruch als die Kompilierung ohne LTCG, vor allem in umfangreichen Anwendungen. In Visual Studio 2010 wurde die LTCG-Kompilierung um bis zu 30 Prozent verbessert. In dieser Version wurde ein dedizierter Thread zum Schreiben von PDB-Dateien eingeführt, sodass bei Verwendung des Schalters /DEBUG Link-Verbesserungen erzielt werden.

PGO-Instrumentierungsläufe wurden durch Hinzufügen von Unterstützung für nicht sperrende Versionen von instrumentierten Binärdateien beschleunigt. Es gibt auch eine neue POGO-Option, PogoSafeMode, mit der Sie bei der Optimierung einer Anwendung den sicheren oder den schnellen Modus angeben können. Der schnelle Modus stellt das Standardverhalten dar. Der sichere Modus ist threadsicher, aber langsamer als der schnelle Modus.

Die Qualität des compilergenerierten Codes wurde verbessert. Es gibt jetzt volle Unterstützung für die für Gleitkomma-intensive Anwendungen wichtigen Advanced Vector Extensions (AVX) auf AMD- und Intel-Prozessoren über intrinsic und /arch:AVX. Gleitkommaberechnungen mit der Option /fp:fast sind genauer.

Erstellen von Anwendungen für Windows 7

Mit Windows 7 wurden zahlreiche wichtige neue Technologien und Features eingeführt und neue APIs hinzugefügt. Visual Studio 2010 bietet Zugriff auf alle neuen Windows-APIs. Die Windows SDK-Komponenten, die zum Schreiben von Code für systemeigene Windows-APIs benötigt werden, sind in Visual Studio 2010 enthalten. Mithilfe der SDK-Header und Bibliotheken aus Visual Studio 2010 können Sie Neuerungen wie Direct3D 11, DirectWrite, Direct2D und Windows-Webdienste-APIs nutzen.

Diese Version von Visual Studio macht nicht nur sämtliche Windows-APIs für Entwickler verfügbar, sondern sie erleichtert auch Ihnen das Schreiben von Anwendungen für Windows mit einem verbesserten MFC. Sie erhalten Zugriff auf grundlegende Windows 7-Funktionen über MFC-Bibliotheken, ohne direkt für systemeigene APIs schreiben zu müssen. Vorhandene MFC-Anwendungen erstrahlen im Glanz von Windows 7 durch einfaches Neukompilieren. Und Ihre neuen Anwendungen können die neuen Features uneingeschränkt nutzen.

MFC enthält jetzt eine verbesserte Integration in die Windows-Shell. Anwendungen können nun wesentlich besser in den Windows-Explorer integriert werden, indem die in dieser Version hinzugefügten Dateihandler für die Vorschau, Miniaturansichten und die Suche verwendet werden. Diese Features stehen als Optionen im MFC-Anwendungs-Assistenten zur Verfügung, wie in Abbildung 6 dargestellt. Das ATL DLL-Projekt, das diese Handler implementiert, wird automatisch von MFC generiert.

Abbildung 6: MFC-Anwendungs-Assistent mit Dateihandleroptionen
Abbildung 6 MFC-Anwendungs-Assistent mit Dateihandleroptionen

Eine der bemerkenswertesten Änderungen der Benutzeroberfläche in Windows 7 ist die neue Taskleiste. Mit MFC können Sie schnell auf Features zugreifen wie Sprunglisten, Miniaturansichten mit Registerkarten, Miniaturbildansicht, Statusanzeigen, Symbolüberlagerung usw. Abbildung 7 zeigt Miniaturbildansichten und Miniaturansichten mit Registerkarten für eine MDI MFC-Anwendung mit Registerkarten.

Abbildung 7: Miniaturansicht mit Registerkarten und Miniaturbildansicht in einer MFC-Anwendung
Abbildung 7 Miniaturansicht mit Registerkarten und Miniaturbildansicht in einer MFC-Anwendung

Die Bandbenutzeroberfläche verfügt nun ebenfalls über ein Menüband im Stil von Windows 7. In einer Anwendung kann die Benutzeroberfläche bei der Entwicklung jederzeit mit der Stildropdownliste von verschiedenen Menübändern im Office-Stil auf das Windows 7-Menüband umgeschaltet werden, wie in Abbildung 8 dargestellt.

Abbildung 8: Menübandstil-Dropdown in einer MFC-Anwendung
Abbildung 8 Menübandstil-Dropdown in einer MFC-Anwendung

MFC ermöglicht Anwendungen mit Mehrfingereingabe und ruft entsprechende Meldungen auf, die Sie behandeln müssen, wenn die verschiedenen Berührungsereignisse auftreten. Wenn Sie sich für Berührungs- und Bewegungsereignisse registrieren, werden diese Ereignisse für Ihre Anwendung weitergeleitet. MFC macht Anwendungen zudem standardmäßig kompatibel mit hohen DPI-Werten, sodass sie auch bei einer hohen Bildschirmauflösung nicht "pixelig" oder unscharf aussehen. Schriftarten und andere Elemente werden von MFC intern so skaliert und geändert, dass die Benutzeroberfläche auch auf hoch auflösenden Bildschirmen scharf dargestellt wird.

Neben den neuen Windows 7-Features wurden nun einige Windows-Features integriert, die seit Windows Vista vorhanden waren, aber in früheren Versionen von MFC nicht enthalten waren. Beispielsweise ist der in Windows Vista eingeführte Neustart-Manager ein nützliches Feature, das einer Anwendung das Speichern der Anwendung ermöglicht, bevor sie beendet wird. Die Anwendung kann dieses Feature aufrufen und dann beim Neustart ihren Status wiederherstellen. Sie können nun den Neustart-Manager in Ihrer MFC-Anwendung nutzen, um Abstürze und Neustarts eleganter zu handhaben. Sie müssen lediglich eine Codezeile hinzufügen, um Neustart und Wiederherstellung in einer vorhandenen Anwendung zu aktivieren:

CMyApp::CMyApp() {
  m_dwRestartManagerSupportFlags = 
    AFX_RESTART_MANAGER_SUPPORT_RESTART;
// other lines of code ...
}

Neue MFC-Anwendungen erhalten diese Funktionalität automatisch durch den MFC-Anwendungs-Assistenten. Der Mechanismus für automatisches Speichern steht für Anwendungen zur Verfügung, die Dokumente speichern. Das Intervall für automatisches Speichern kann vom Benutzer definiert werden. Im MFC-Anwendungs-Assistenten kann für Anwendungen nur die Unterstützung für Neustart oder der Start mit Anwendungswiederherstellung ausgewählt werden (nur für Anwendungen mit Dokumentenanzeige).

Eine weitere Neuerung ist das Windows-Aufgabendialogfeld, ein verbessertes Meldungsfenster (siehe Abbildung 9). MFC verfügt nun über einen Wrapper für das Aufgabendialogfeld, den Sie in Anwendungen verwenden können.

Abbildung 9: Aufgabendialogfeld
Abbildung 9 Aufgabendialogfeld

Der MFC-Klassen-Assistent ist zurück

In die MFC-Bibliothek wurde nicht nur neue Funktionalität aufgenommen, auch die Verwendung von MFC in der Visual Studio-IDE wurde vereinfacht. Ein häufig gefordertes Feature, der MFC-Klassen-Assistent (siehe Abbildung 10), wurde verbessert wieder eingeführt. Sie können einer Anwendung mit dem MFC-Klassen-Assistenten nun Klassen, Ereignishandler und andere Elemente hinzufügen.

Abbildung 10: MFC-Klassen-Assistent
Abbildung 10 **MFC-Klassen-Assistent **

Eine weitere Neuerung ist der Menüband-Designer, mit dem Sie die Bandbenutzeroberfläche grafisch entwerfen (statt in Code, wie in Visual Studio 2008) und als XML-Ressource speichern können. Dieser Designer ist nicht nur nützlich zum Erstellen neuer Anwendungen, auch die Benutzeroberfläche vorhandener Anwendungen kann damit aktualisiert werden. Die XML-Definition kann erstellt werden, indem der vorhandenen Codedefinition der Bandbenutzeroberfläche vorübergehend folgende Codezeile hinzugefügt wird:

m_wndRibbonBar.SaveToXMLFile(L"YourRibbon.mfcribbon-ms");

Die resultierende XML-Datei kann dann als Ressourcendatei verarbeitet werden. Weitere Änderungen können mit dem Menüband-Designer vorgenommen werden.

Zusammenfassung

Visual Studio 2010 stellt eine Hauptversion in der Entwicklung von Visual C++ dar und erleichtert Entwicklern das Leben in verschiedener Hinsicht. In diesem Artikel wurden einige Verbesserungen in Bezug auf C++ nur sehr oberflächlich behandelt. Weitere Erläuterungen zu verschiedenen Features finden Sie in der MSDN-Dokumentation und im Visual C++-Teamblog unter blogs.msdn.com/vcblog, der auch die Grundlage für einige Abschnitte dieses Artikels bildete.   

Sumit Kumar ist Program Manager im Visual C++-IDE-Team. Er hat einen Master-Abschluss in Informatik von der University of Texas in Dallas.

Unser Dank gilt folgenden technischen Experten:Stephan T. Lavavej, Marian Luparu und Tarek Madkour