3

As much as I enjoy C++ programming, there is one thing I really don't get. To me, it seems that the most common way of programming a function is like this:

some_function(a variable)
    do something according to the data in the variable

example:

bool match_name(const std::string& name)
{
    return this->name == name;
}

I find myself using const ref for 90% of all function parameters in my code (maybe I'm doing something wrong).

My question is: Why is a copy of the variable the "default" type of parameters? Why are not const ref the default?

Why not something like?:

void my_function(My_type object)      // <- const ref

void my_function(ref My_type object)  // <- ref (mutable)

void my_function(copy My_type object) // <- copy
birgersp
  • 3,909
  • 8
  • 39
  • 79
  • 2
    `void my_function(My_type object) // <- const ref` that would make it extremely painful to get C code to run in C++. – Blaze Mar 04 '20 at 09:21
  • One big thing here is that references didn't even exist when the language was first created. And changing basic syntax is a big deal breaker in most cases. Not to mention the backwards compatibility. – super Mar 04 '20 at 09:23
  • 3
    Because C++ started as C with classes – bolov Mar 04 '20 at 09:24
  • I guess, historically (and in the days of C), many (if not most) functions weren't expected to modify their arguments, but to return a calculated value, instead. E.g. standard functions like `sqrt(x)`, `sin(x)` and (if implemented as a function rather than as a macro) `max(a,b)`. – Adrian Mole Mar 04 '20 at 09:24
  • Because Bjarne Stroustrup. Because 1981. Because if it was the default there would be no way to get the current behaviour. Because because because. – user207421 Mar 04 '20 at 09:33
  • 2
    C++ has lot of wrong default, Rust uses move by default, has `mut`(able) instead of `const`, ... – Jarod42 Mar 04 '20 at 09:36
  • Here is some example, where passing by const reference causes unexpected behavior: http://coliru.stacked-crooked.com/a/69cde8c13917b015 – VLL Mar 04 '20 at 09:39
  • I mean, in the end, you'd save some seconds typing in the `const` and the `&`, while somewhat obfuscating what the true type is. In any case, it would come down to what is essentially syntactic sugar. Writing out the stuff is not that big of a deal - I'm even considering always writing the `std::`, even for the most standard types, like no `using std::vector;`. I'd rather think about changing the syntax in regard to pointers and `const` (`const type *` vs `type const *` vs `type * const` - at least at the beginning, you have to look up what is what, not exactly intuitively). – Aziuth Mar 04 '20 at 10:05
  • @Jarod42 move by default? Like, "std::string x=myOldString;` that would be implicitly `std::string x=std::move(myOldString);`? – Ruslan Mar 04 '20 at 10:15
  • @Ruslan: Yes, and they check that moved-from objects are not reused. and you have [clone traits](https://doc.rust-lang.org/std/clone/trait.Clone.html) for explicit copy. – Jarod42 Mar 04 '20 at 10:30
  • @Jarod42 doesn't look like a safe default. The compiler can't check this at compilation time in general. This is basically why `std::auto_ptr` was removed. – Ruslan Mar 04 '20 at 10:49
  • @Ruslan: They have advanced checker, they have also lifetime in type system. Those checks are done at compile time, but that indeed forbids code which would be valid and correct in C++. – Jarod42 Mar 04 '20 at 10:54

2 Answers2

5

Apart from historical reasons, that would have very ugly implications. It basically means that MyType myVarName would have different meaning when is scope of function or when used in function argument list.

void foo(MyType myVar) //myVar is const ref
{
    MyType anotherVar = myVar; // anotherVar is a copy, what?
    const MyType& myVarRef = myVar; //a reference, but it looks like it has different type than myVar?
}

And think of the poor &! Unless you propose a different way of distinguishing refs from non-refs, you end up with

void foo(MyType& myVar) //myVar is a copy?
{
    MyType& anotherVar = myVar; // anotherVar is a reference???
}

C++ has a well established notion of object and reference to object. The latter is always denoted by adding & to type. Your proposition would mess that up and C++ would be even harder to understand than it is.

Yksisarvinen
  • 18,008
  • 2
  • 24
  • 52
  • If C++ changed to sue the "ref" and "copy" keywords, the `MyType anotherVar = myVar;` would also make `anotherVar` a const ref. To make a copy, it could probably be `copy MyType anotherVar = myVar;` or something like that... – birgersp May 08 '20 at 06:14
3

C++ wants to be compatible with C, and since C uses value semantics by default, so does C++. It would be very inconsistent to take build-in types by value but other types by reference.

However, passing by reference is not always better. The pointer indirection a reference causes generally makes it harder for the compiler to optimize code gen. In many cases, passing a type by value is still better than by ref. It depends on the type. The pointer indirection of references might cause a memory read, while passing by value allows the object to be passed in CPU registers. For example, a type like this:

class Point {
public:
    /* functions */
private:
    int x;
    int y;
};

Should always be passed by value. It's just two integers. A reference might cause unneeded memory reads to get to the values.

Also, sometimes you want to pass by value even if the type cannot be enregistered. So called "sink functions" for example that need to store the passed value perform better with values due to move semantics. Prime examples are constructros and setters:

void SomeClass::setString(std::string s)
{
    this->s_ = std::move(s);
}

Passing by reference in this case can incur extra copies. You can read up more on this online. A good starting point is this SO question:

Are the days of passing const std::string & as a parameter over?

Nikos C.
  • 50,738
  • 9
  • 71
  • 96