C++ When does it make sense to pass a const struct parameter by value vs. reference?

Viewed 1516

I've seen a similar question to this, but i'd like some clarification...

Assuming a basic C++ class:

class MyClass
{
public:
    struct SomeData
    {
        std::wstring name;
        std::vector<int> someValues;
    };

    void DoSomething(const SomeData data);
}

I understand that data will be passed as const to DoSomething and that is ok since data will not be modified in any way by the function...but I am used to seeing & specified with const parameters to ensure that they are passed by reference, e.g.

void DoSomething(const SomeData& data);

That seems more efficient to me. If we omit the &, then isn't data being passed by value to DoSomething? I'm not sure why it would ever be preferable to pass a const parameter by value when you can pass by reference and avoid the copy occurring?

4 Answers

Pass by value/reference and const-correctness are two different concepts. But used together.

Pass by Value

 void DoSomething (SomeData data);

Pass by value is used when it is less costly to copy and do not want to keep references to foreign objects. This function could (if it is inside a class) keep a pointer to this in some case and have its own copy.

Pass by reference

void DoSomething (SomeData& data);

Always use pass by reference if you know this might cause a performance loss copying the struct. This function could (if it is inside a class) keep a pointer to this in some case and pointing to a foreign object. Keeping pointers to foreign objects mean you should aware of its life-time and when this foreign object goes out of bound. More importantly changes to foreign object appears to your pointer.

const correctness

void DoSomething (const SomeData data);  // const for local copy
void DoSomething (const SomeData& data); //  const for callers object

Adding constto pass by value or reference means this function does not change it. But not having or having & decides which object you are trying to add safety of modifying. const is a very helpful tool in C++ in terms of Documenting APIs, provide compile time safety, allow more compiler optimizations.

Read this article.

The biggest problem with void DoSomething(const SomeData data) is that it conflates interface and implementation. From the caller's point of view, the const doesn't change anything; the function receives a copy anyway, and the original object is not modified. What the implementation does or does not with its own, function-internal copy should not bother the caller and should thus not be expressed in the interface.

The const does make the implementation more const-correct if the copy is not changed, but leaking implementation details into the interface is a high price to pay. I recommend not using void DoSomething(const SomeData data).

As always, performance gains or losses should not be overestimated here. It's more about semantics and conventions.

Passing a const value is mostly informational for the caller, it shows intent. This is important to make code easy to read, understand and maintain.

It might also be possible for the compiler to add some extra optimizations if it knows that the function doesn't modify its argument. For example it might cause the compiler to not perform a copy at all.

This:

void DoSomething(const SomeData data);

is a bit unusual, because while it does not change anything for the caller (who can pass a const or non-const value), it restricts what the function can do internally. There's not a lot of value in that, and it's not commonly done.

Passing by reference (const or not) is more efficient if the value is expensive to copy, including if it is larger than approximately two pointers on the target platform. In other words, if SomeData were a struct containing two integers it would probably be more efficient to pass it by value. But if it contains a std::map or some larger data, better pass it by reference.

An exception to this is if the function is going to copy the value anyway, then it is better to take it by value, because the value might be "moved" instead of copied if the caller allows it.

Related