Please review the following as needed:
Data passed into or returned from a function by value creates a new
Alternatively, pass and return by pointer or by reference is independent of the data size - it takes the same amount of time to pass a large data structure as it does to pass a simple variable. Both mechanisms maintain a single copy of the data but give it two names - one defined in the calling scope and one in the function's scope. Changes to the data made through one name are visible through both names. So, pointers and references implement a two-way flow of data.
Sometimes, solving a problem calls for the speed and efficiency of pointers or references while simultaneously providing the security of copying the data. The const
keyword introduced in Chapter 6 allows programmers to write fast and secure code. This section extends const
to member functions.
Unmodified, pass-by-pointer and by-reference create an INOUT data flow, but the const
keyword restricts them to an IN only flow. Chapter 6 illustrated how to apply const
to references and pointers. With a small extension of the syntax, we also make the object that calls or is bound to a member function (see Figure 1) "constant" or unchangeable. When we place the const
keyword after the argument list, it applies to the this
pointer, making the object to which it points (the object bound to the function call) constant.
Function Calls | Function Prototypes | Representations |
---|---|---|
b.function1(f); |
void function1(const Foo& a_f); |
|
(a) | ||
b.function2(&f); b.function3(&f); b.function4(&f); |
void function2(const Foo* a_f); void function3(Foo* const a_f); void function4(const Foo* const a_f); |
|
(b) | ||
b.function5(); |
void function5() const; |
|
(c) |
const
keyword determines to which parameter it applies. The above code fragments assume that Foo is the name of a class, that f is a Foo object, and that b is another object (an instance of Foo or some other class) that defines the called functions. Including the const
keyword as a part of the parameter definition makes the parameter constant: any attempt to change it in the function will result in a compile-time error. The const
keyword applies to individual parameters, so programmers must apply it to each parameter the function should hold constant.
const
usage - preventing functions from changing data passed as a pointer.this
parameter is always passed by pointer. If it must not change, the const
keyword is placed at the end of the parameter list, outside and following the last parenthesis. In the function definition, the opening brace of the function body follows const
. Any attempt to change this in the function will also result in a compile-time error.To maintain strong encapsulation, programmers typically make member variables private
. However, an application that uses a class may sometimes need to access a member variable. Access functions, getters and setters, are the most common solution. Setter functions can validate the data - ensuring it is within bounds or formatted correctly - before storing it in an object. Getter functions allow an application program to "see" the value stored in a member variable. A well-formed getter operates as an OUT-only function, preventing the application from changing and possibly corrupting the object.
Like any function, getters generally perform a return-by-value, that is a return-by-copy. So, any change the application makes to the returned data only affects the copy and leaves the original data - still safely encapsulated in the object - untouched. The next figure illustrates this common behavior.
class Person { private: string name; int height; public: Person(string n, int h) : name(n), height(h) {} string get_name() { return name; } int get_height() { return height; } }; |
int main() { Person p("Alice", 65); string local_name = p.get_name(); cout << local_name << endl; local_name = "Carol"; // doesn't change p cout << p.get_name() << endl; cout << local_name << endl; return 0; } |
(a) | (b) |
Alice Alice Carol
But what happens if we change the name from a string to a C-string? Recall that arrays, and therefore C-strings, are always passed to and returned from functions by-pointer. Pointers implement a two-way data flow that can potentially weaken encapsulation by allowing an application to change the data saved in an object. The next example replaces string name;
with char name[100]
and, correspondingly, the return type of get_name() to char*.
class Person { private: char name[100]; int height; public: Person(char* n, int h) : height(h) { strcpy_s(name, 100, n); } char* get_name() { return name; } int get_height() { return height; } }; |
int main() { Person p("Alice", 65); char* local_name = p.get_name(); cout << local_name << endl; strcpy_s(local_name, 100, "Carol"); // changes p cout << p.get_name() << endl; cout << local_name << endl; return 0; } |
(a) | (b) |
Alice Carol Carol
We can restore the strong encapsulation demonstrated by Figure 2 even while continuing to store the person's name as a C-string by making the pointer returned by get_name() a constant.
class Person { private: char name[100]; int height; public: Person(char* n, int h) : height(h) { strcpy_s(name, 100, n); } const char* get_name() { return name; } int get_height() { return height; } }; |
int main() { Person p("Alice", 65); const char* local_name = p.get_name(); cout << local_name << endl; strcpy_s(local_name, 100, "Carol"); // compile-time error cout << p.get_name() << endl; cout << local_name << endl; return 0; } |
(a) | (b) |
const
keyword to the function's header. As illustrated previously, where the programmer places the const
keyword impacts what feature is made constant. Placing const in this location makes the return value constant.const
to the variable definition results in a compile-time error. Any attempt to change a constant variable (demonstrated with a call to the strcpy_s
function in this example) also results in a compile-time error.