New Age C++

What .NET Developers Must Know about C++ Classes

C++ does things differently than C# or Visual Basic, especially when it comes to class construction. Take this tour to learn about the differences.

C++ has typical idioms and conventions that are expressed differently in C# or Java, if at all. Sometimes it's a matter of syntax, other times it's a conceptual consideration related to the absence of a managing runtime in native languages.

I'll explain some relevant differences in this post while showing more new C++11 features. You'll need an alternative tool to compile many of these samples, as Visual C++ 2012 isn't C++11 compatible. (Note that those instances of incompatibility with Visual C++ 2012 are marked with an asterisk (*) throughout this article). I used the MinGW toolchain with the Code::Blocks IDE to test the samples here.

Objects, Not References
In managed languages, an object variable is actually a reference to an object, as shown in Listing 1.

When variable my2 is assigned my1, both start referring to the same MyClass instance. In C++, an object variable is a true object in the stack, rather than a reference to an object in the application heap. Assignments between objects imply copies, which is why Listing 2 would seem to be nothing more than the C++ version of Listing 1. When run, however, the difference is noticeable. In the C# version, both variables live in the stack, although the instance to which they refer is in the garbage-collected heap. An equivalent native version of the C# sample is shown in Listing 3.

The dot ('.'), the class member access operator in C#, is the C++ arrow ('→'), which dereferences a pointer before accessing an instance member. To see how it behaves, change the definition of the C# MyClass by making it a C# struct. Run it and see the change. Don't overlook this semantic difference when involving objects in assignments, especially if you're porting managed applications to C++.

Instance Construction
Managed languages use the operator new to construct class instances. In C++, new is used only to create instances in the heap (like in Listing 3, although the new happened inside make_shared). In Listing 2, I constructed my1 and my2 without explicit constructor invocations; I just passed arguments between brackets right after declaring each.

This new C++11 notation calls the proper constructor overload*. Parentheses were used before, instead of brackets, but it made code more confusing, as it seemed like calling function my1, and passing 5 as the argument.

MyClass my1(5); // is my1 a MyClass variable or a  function?

The constructor defined in MyClass built my1. An implicit constructor known as the "copy constructor" built my2 with my1 as argument.

Given a T class, a copy constructor is implicitly available. By default, it constructs an object whose members are identical to those of the received argument. You can explicitly redefine this behavior. In C++11, it can be omitted by declaring this:

 
T(const T&) = delete;  // (Not available in Visual C++ 2012)

C++, Java and C# implicitly define a default constructor -- that takes no parameters and does nothing -- when the developer doesn't define other constructors. If this isn't what you want, you can explicitly define it as private in managed languages. In C++11, you can "T() = delete" it*.

In all three languages, the default constructor is omitted when you make a constructor explicit; in C++11, you can bring it back with the following:

T() = default; // equivalent to T() {}; (Not  available in Visual C++ 2012) 

The new notation seems longer than the previous one, doesn't it? It actually isn't, because in C++ you must distinguish the declaration of classes, structs and functions from their definition.

A class declaration is the minimum amount of information other types need available at compile time. It's a structural outline of class members (fields, function contracts), usually coming in header files. The implementation of these member functions -- called methods in managed languages -- completes the class definition and goes in .cpp files.

In managed languages, a class declaration is implied in its definition. I could do the same in C++; indeed, I did it in Listing 2. The drawback, though, is that every time I change a function definition in an included file, all source code including the definition will get unnecessarily recompiled. See Listing 4 for a real-world version of Listing 2 that splits declaration from definition. By declaring T() = default in the header file, I wouldn't need to define it in the .cpp file.

Before C++11, it wasn't possible for a constructor to delegate tasks into another one of the same type or its direct base class. You can do it in C# or Java by calling this, base or super, respectively. It's now possible in C++11* by using the name of the class or base class, respectively (Listing 5).

Member Initialization
Managed code developers may have found some unusual notation in the way field myField_ is initialized in MyClass constructor in Listing 2. Why isn't it inside the constructor body like this?

MyClass(const int fieldInitValue) {
  myField_ = fieldInitValue;
}

The answer is especially relevant when class fields are constructible classes or structs. Putting the logic inside the body implies that these members are constructed first, so you must ensure default constructors for each. They get values assigned when the body starts execution.

Instead, if they're initialized as in Listing 2, they're constructed by the constructor that corresponds to the initialization arguments between brackets, all at once. These brackets are a uniform C++11 notation to initialize variables (whether class instances or PODs --plain old data types)*. This is complemented with initializer-list constructors, shown in Listing 6. They enable a concise, elegant notation to initialize containers, similar to C# collection initializers for IEnumerable classes*. Notice the simplicity in sentences like these:

Person moe = { "Moe", "Howard" };

VectorPersonList vpl = { moe, larry, curly };
Simple and Concise Constructors
C++ object variables live in the stack; assignments imply copies rather than co-referencing. C++ decouples class declaration from definition, as most decisions happen at compile time. C++11 enables a simplified and concise class constructor declaration, allowing delegation and a flexible initialization.

About the Author

Diego Dagum is a software architect and developer with more than 20 years of experience. He can be reached at [email protected].

comments powered by Disqus

Featured

Subscribe on YouTube