hookenz
hookenz

Reputation: 38889

What's the correct way to use const in C++?

const correctness has me somewhat confused.

What rule of thumb do you use to decide when something should be const or not?

e.g. consider this example

class MyClass
{
  string ToString(); // this one?
  const string& ToString(); // or this? 
  const string& ToString() const; // or this?

  char* ToString(); // What about this?
  const char* ToString(); // or this?
  const char* ToString() const; // or this?
  const char const* ToString(); // Is this legal?
  const char const* ToString() const; // how about this?
  char const* ToString();  // or even this?
};

Const can get really confusing.

What would be the difference between all these ToString methods?

If I understand correctly, the first one returns a new string object that can be modified if need be. The second one returns a constant reference maybe it should be string const& ToString(). The third one is probably a nonsense because references are always constant is that correct?

Thought I'd throw the old char* versions in there for comparison as I do have methods that return object pointers and I'm not sure whether they should be const or not.

I guess I'm just trying to understand the limitations and benefits of const correctness and how to decide up front whether something should be const or not and how to correctly apply const since placing const in different places changes the meaning.

EDIT: also, how do I deal with that '... discards qualifiers'. What does that actually mean?

Upvotes: 5

Views: 960

Answers (6)

Arun
Arun

Reputation: 20383

Some variations in the question is on returning string versus char *. That I think is unrelated to the const discussion (let me know if it isn't); I am considering the string return variations.

class MyClass
{
  string ToString();              // A
  string & ToString();            // B   (I added this)
  const string& ToString();       // C 
  const string& ToString() const; // D
};

My preferences are in this order: D, C, B, A.

In all the variations, const are used in two ways:

  • return type specifier [this first const in D]

    This means the returned object cannot be used to modify the returned object. The sentence sounds funny, doesn't it? Well, there may be other ways of modifying an object and this const cannot stop that. See this FAQ item.

  • class invariant [the second const in D]

    This means that the method does not mutate (modify) the class object.

I prefer D over anything else because D guarantees that the object on which the method is invoked won't be modified. If an objective can be achieved (i.e. method can be implemented) without modifying an object, thats a big win in terms of design. I use it whenever I can. But if the object has to be modified (i.e. there is no way the method can be implemented without modifying the object), then D is ruled out.

Among the remaining, both B and C are preferable over A because B and C returns a reference, which avoids the copy construction required in A.

Between B and C, C is preferable because it is returning a const reference.

Upvotes: 0

André Caron
André Caron

Reputation: 45249

What rule of thumb do you use to decide when something should be const or not?

Use it everywhere you can. Then, don't use it when you nedd to modify the object or grant access to something that may modify the object (i.e. returning references or proxies to an internal state).

The third one is probably a nonsense because references are always constant is that correct?

No, that is not correct. A reference is an alias, not a variable. Therefore, you cannot change which variable a reference "points" to, like you can with a pointer. However, you may have a reference to a mutable object (std::string&).

What would be the difference between all these ToString methods?

They all pretty much differ on memory management techniques, but at a high-level, they all do the same thing except for the following:

char* ToString();

That ont returns a pointer to a mutable array of chars (presumably internal state).

Note that the family of:

char const* ToString();
const char* ToString(); // or this?
const char const* ToString(); // Is this legal?

are all different ways to write the same thing. Native types are all const when returned by value whether you write it or not.

The following 2 are the preferred way (provided an extra const at the end) of returning strings in C++:

string ToString(); // this one?
const string& ToString(); // or this?

Which one of the two you will use depends on where you get the value from. If the string is a data member, I suggest you go for the latter because it is usually faster, though not so much if your string implementation uses Copy-On-Write semantics. If you have to compute a value and return it, you have to use the former because you cannot return a reference to a local variable.

The following two are correct, but I still recommend you use std::string

const char* ToString() const; // or this?
const char const* ToString() const; // how about this?

Upvotes: -1

aschepler
aschepler

Reputation: 72271

You can't overload functions which have the same name and same argument but different return type. You probably knew that, but just making sure.

const after the () parentheses means that the function will not change the object you call it on. Usually something called ToString doesn't change the object, so in all cases you probably want a const method.

The difference between returning string and returning const string& is that the reference doesn't make and object copy and may be faster, but you can only do it if you already have a string object (for example, as a private member of MyClass). If not (say you need to piece a few bits of data together and then return that string), you'll need to return the string by value.

Using string objects is usually preferable to using C-style char* pointers, but since you ask: The fourth one, char*, would let other code change the characters within the returned string, which is probably a bad thing. const char*, char const* and const char const* are all the same thing. char *const is technically different, but it wouldn't do much as a return type for the same reason that returning a const int doesn't matter much: the caller can just copy and then change the return value.

In short, the best forms would be, in order:

const string& ToString() const;
string ToString() const;
const char* ToString() const;

Upvotes: 0

Zooba
Zooba

Reputation: 11438

Where you use const depends on the purpose of the function. As James suggests in his comment (which is worth putting as an answer), put const anywhere you can:

If the function is intended to modify state within it's object instance, don't put const at the end of the signature.

If the function is intended to modify one of it's reference or pointer parameters, don't put const on the parameter.

If the variable referenced by a pointer or reference should be modified, don't put const on the type (remember, const applies to the part of the definition immediately prior).

If the returned reference/pointer references a variables that should not be changed by the received, do put const on the type.

Answering the examples given in the question is impossible without knowing the purpose of the functions. My tendency would be to use string ToString() const and char* ToString() const, with very clear documentation on who is responsible for deleteing the char*.


As an extra note, const char* and char const* are identical (pointer to unmodifiable characters). char* const, on the other hand, is an unmodifiable pointer to modifiable characters.

Upvotes: 2

MykC
MykC

Reputation: 188

I've always used this FAQ for these types of questions as a starting point. http://www.parashift.com/c++-faq-lite/const-correctness.html

Upvotes: 1

Jeff Barger
Jeff Barger

Reputation: 1239

I've found this to be a helpful guide

Upvotes: -1

Related Questions