Etiquetas

12/09/2014

C++: consts and const references and const pointers and const methods, oh my!

C++ is powerful. But it has some concepts that seem easy to understand... until you actually use them for the first time. This is a compilation of some of my head-scratching moments while grasping C++ capabilities that are foreign to C programmers like me.

References

Declare a variable with "&" and it is a reference. Think of a C pointer ("*"), but it can not be NULL and you can not cast it to reference another type (the case of C's malloc() returned void pointer). Examples:
    // With a basic type
    int number = 3;
    int &reference = number;
    cout << reference << endl; // Prints 3
    reference = 1;
    cout << number << endl; // Now it prints 1

    // With a class instance
    string stringInstance = "Hi!";
    string &stringReference = stringInstance;
    cout << stringReference.size() << endl; // Note we are not using the "->"
                                            //  operator! It is not a pointer.
References in method declarations allow you to pass the instance itself instead of the value
/* Reverse a string in place, avoiding copies */
void reverse(string input){
    for (int i = 0, j = input.size() - 1; i < j; ++i, --j){
        char tmp = input[i];
        input[i] = input[j];
        input[j] = tmp;
    }
}
That didn't do anything to the real "input" string. This is C++, so "input" is an instance of the class "string" and not a pointer. The method reverse() has been working on a copy of the class instance that was destroyed as soon as the method finished! Solution? We could pass a pointer (string *input) and go crazy like in C, but then we would have to check for NULL pointers, use the "->" operator and who knows what else. The C++ way? Use a reference!
/* Reverse a string in place, avoiding copies */
void reverse(string &input){
    for (int i = 0, j = input.size() - 1; i < j; ++i, --j){
        char tmp = input[i];
        input[i] = input[j];
        input[j] = tmp;
    }
}
No changes required to the method body. It is also required for optimization, cause it avoids a deep copy of the instance's internal structure.

Const

The qualifier "const" (which also exists in C) can be mostly read as "readonly" (think Java's "final"). It effects whatever is on its left. If there is nothing on its left, then it effects the first thing on its right. So this is a mistake:
const int const *p = ...; // Compiler error, duplicate const!
Now the caveats:
    const int number = 2; // int value is readonly now
    int &reference = number; // Compiler error: "int &" requires
                             // an "int", not a "const int"
Fix:
    const int number = 2;
    const int &reference = number; // But now "reference = 1;" is an error, careful!
Note that "const int & reference" and "int const & reference" is the same thing (remember, left unless there's nothing, if so, right). What if we start moving the const modifier around?
   int & const reference = number; // Compiler error: const attempts
                                    // to modify the reference itself (&),
                                    // but references are always readonly!

Const and pointers

A C refresher. Remember: const affects that on its left, including the pointer (*) itself.
    // With a basic type
    int number = 2;
    const int *p = &number;
    *p = 3;   // Compiler error: the value is readonly!
    int * const pp = &number;
    *pp = 3;    // OK
    pp = &argc; // Compiler error, the pointer is readonly!

Const and method declarations

Const applies also to method arguments:
void clearString(const string & input){
    cout << "Deleting this many characters: " << input.size() << endl;
    input.clear();  // Compiler error: the instance is readonly!
}
Wait... how did the C++ compiler know the method clear() would change the string, and size() wouldn't? Hardcore static code analysis? AI? Magic? Not really. Const has another use: specifying which member methods in a class change no values and hence are safe to be called when the class is readonly. This is the declaration of the size() method in string, note the const after the parenthesis:
// Capacity:
///  Returns the number of characters in the string, not including any
///  null-termination.
size_type size() const
{
    return _M_rep()->_M_length;
}
That use of const is exclusive to objects. It will throw a compiler error if you try to use it in a function declaration that does not belong to a class.

Done for today. Hope it helped you!