Ensuring That a Function Doesnt Modify an Argument

Ensuring That a Function Doesn t Modify an Argument

Problem

You are writing a function, and you need to guarantee that its arguments will not be modified when it is invoked.

Solution

Declare your arguments with the keyword const to prevent your function from changing the arguments. See Example 15-3 for a short sample.

Example 15-3. Guaranteeing unmodified arguments

#include #include void concat(const std::string& s1, // These are declared const, so they const std::string& s2, // cannot be changed std::string& out) { out = s1 + s2; } int main( ) { std::string s1 = "Cabo "; std::string s2 = "Wabo"; std::string s3; concat(s1, s2, s3); std::cout << "s1 = " << s1 << ' '; std::cout << "s2 = " << s2 << ' '; std::cout << "s3 = " << s3 << ' '; }

 

Discussion

Example 15-3 demonstrates a straightforward use of const. There are a couple of good reasons for declaring your function parameters const when you don't plan on changing them. First, you communicate your intent to human readers of your code. By declaring a parameter as const, what you are saying, essentially, is that the const parameters are for input. This lets consumers of your function, code with the assumption that the values will not change. Second, it tells the compiler to disallow any modifying operations, in the event you do so by accident. Consider an unsafe version of concat from Example 15-3:

void concatUnsafe(std::string& s1, std::string& s2, std::string& out) { out = s1 += s2; // Whoops, wrote to s1 }

Despite my fastidious coding habits, I have made a silly mistake and typed += when I meant to type +. As a result, when concatUnsafe is called, it will modify the arguments out and s1, which may come as surprise to the userwho would expect a concatenation function to modify one of the source strings?

const to the rescue. Create a new function concatSafe, declare the variables const as in Example 15-3, and it won't compile:

void concatSafe(const std::string& s1, const std::string& s2, std::string& out) { out = s1 += s2; // Now you will get a compile error }

concatSafe guarantees that the values in s1 and s2 will remain unchanged. It also does something else: it allows the user to pass const arguments. For example, code that needs to concatenate strings might look like this:

void myFunc(const std::string& s) { // Notice that s is const std::string dest; std::string tmp = "foo"; concatUnsafe(s, tmp, dest); // Error: s is const // Do something with dest... }

In this case, myFunc won't compile because concatUnsafe does not maintain the const-ness guarantee of myFunc. myFunc has made a guarantee to the world that it won't modify the contents of s, which means that anything done to s within the body of myFunc must uphold this promise. Of course, you can get around this by using const_cast to cast away the const-ness, but that is just playing fast and loose with your variables, so you should avoid it. concatSafe compiles and runs fine in this situation.

Pointers add a wrinkle to this otherwise rosy portrait of const. When you declare a pointer variable as a parameter, there are two parts to it: the address itself and the thing that address refers to. C++ lets you use const to constrain what you can do to either one of these values. Consider yet another concatenation function that uses pointers:

void concatUnsafePtr(std::string* ps1, std::string* ps2, std::string* pout) { *pout = *ps1 + *ps2; }

This has the same problems as concatUnsafe, described earlier. Add const to guarantee that the target strings aren't updated:

void concatSaferPtr(const std::string* ps1, const std::string* ps2, std::string* pout) { *pout = *ps1 + *ps2; }

Great, now you can't change *ps1 or *ps2. But you can still change ps1 or ps2, or in other words, you can point them to some other string by changing the value of the pointer, not the value it points to. There's nothing to stop you, for instance, from doing this:

void concatSaferPtr(const std::string* ps1, const std::string* ps2, std::string* pout) { ps1 = pout; // Uh-oh *pout = *ps1 + *ps2; }

Prevent this sort of mistake by using const yet again:

void concatSafestPtr(const std::string* const ps1, const std::string* const ps2, std::string* pout) { *pout = *ps1 + *ps2; }

By using const on either side of the asterisk, you have made your function as safe as it can be. This makes your intentions clear to consumers of your function, and it keeps you honest just in case you make a typo.

See Also

Recipe Recipe 15.4

Категории