Reputation:
In C++ when arguments are strings, is there any noticeable speed/efficiency gained by making them references to string objects just like is done when arguments are objects of a class created by the programmer? For example, if I had a class called Person that had a first and last name, I could do for one of the constructors:
Person(const string& firstName, const string& lastName);
vs.
Person(string firstName, string lastName);
Is the first one more efficient, or is the efficiency gained so trivial it doesn't matter?
Upvotes: 0
Views: 659
Reputation: 310930
Instead of these two approaches
Person(const string& firstName, const string& lastName);
and
Person(string firstName, string lastName);
a more efficient approach is when you have the following two constructors together
Person(const string &firstName, const string &lastName);
Person( string &&firstName, string &&lastName );
This approach
Person(const string& firstName, const string& lastName);
is less efficient when the arguments are rvalues.
This approach
Person(string firstName, string lastName);
is less efficient when the arguments are lvalues.
Here are two demonstrative program that show differences between the three approaches.
#include <iostream>
struct A
{
static int value;
explicit A() : x( ++value )
{
std::cout << "explicit A(), x = " << x << "\n";
}
A( const A &a ) noexcept : x( ++value )
{
std::cout << "A( const A & ), x = " << x << "\n";
}
A( A &&a ) noexcept : x( ++value )
{
std::cout << "A( A && ), x = " << x << "\n";
}
~A()
{
std::cout << "~A(), x = " << x << "\n";
}
int x;
};
int A::value = 0;
struct B
{
B( const A &a1, const A &a2 ) : a1( a1 ), a2( a2 )
{
std::cout << "B( const A &, const A & )\n";
}
A a1, a2;
};
struct C
{
C( A a1, A a2 ) : a1( std::move( a1 ) ), a2( std::move( a2 ) )
{
std::cout << "C( A, A )\n";
}
A a1, a2;
};
struct D
{
D( const A &a1, const A &a2 ) : a1( a1 ), a2( a2 )
{
std::cout << "D( const A &, const A & )\n";
}
D( A &&a1, A &&a2 ) : a1( std::move( a1 ) ), a2( std::move( a2 ) )
{
std::cout << "D( A &&, A && )\n";
}
A a1, a2;
};
int main()
{
A a1;
A a2;
std::cout << '\n';
B b( a1, a2 );
std::cout << "b.a1.x = " << b.a1.x << ", b.a2.x = " << b.a2.x << '\n';
std::cout << '\n';
C c( a1, a2 );
std::cout << "c.a1.x = " << c.a1.x << ", c.a2.x = " << c.a2.x << '\n';
std::cout << '\n';
D d( a1, a2 );
std::cout << "d.a1.x = " << d.a1.x << ", d.a2.x = " << d.a2.x << '\n';
std::cout << '\n';
return 0;
}
The program output is
explicit A(), x = 1
explicit A(), x = 2
A( const A & ), x = 3
A( const A & ), x = 4
B( const A &, const A & )
b.a1.x = 3, b.a2.x = 4
A( const A & ), x = 5
A( const A & ), x = 6
A( A && ), x = 7
A( A && ), x = 8
C( A, A )
~A(), x = 6
~A(), x = 5
c.a1.x = 7, c.a2.x = 8
A( const A & ), x = 9
A( const A & ), x = 10
D( const A &, const A & )
d.a1.x = 9, d.a2.x = 10
~A(), x = 10
~A(), x = 9
~A(), x = 8
~A(), x = 7
~A(), x = 4
~A(), x = 3
~A(), x = 2
~A(), x = 1
And
#include <iostream>
struct A
{
static int value;
explicit A() : x( ++value )
{
std::cout << "explicit A(), x = " << x << "\n";
}
A( const A &a ) noexcept : x( ++value )
{
std::cout << "A( const A & ), x = " << x << "\n";
}
A( A &&a ) noexcept : x( ++value )
{
std::cout << "A( A && ), x = " << x << "\n";
}
~A()
{
std::cout << "~A(), x = " << x << "\n";
}
int x;
};
int A::value = 0;
struct B
{
B( const A &a1, const A &a2 ) : a1( a1 ), a2( a2 )
{
std::cout << "B( const A &, const A & )\n";
}
A a1, a2;
};
struct C
{
C( A a1, A a2 ) : a1( std::move( a1 ) ), a2( std::move( a2 ) )
{
std::cout << "C( A, A )\n";
}
A a1, a2;
};
struct D
{
D( const A &a1, const A &a2 ) : a1( a1 ), a2( a2 )
{
std::cout << "D( const A &, const A & )\n";
}
D( A &&a1, A &&a2 ) : a1( std::move( a1 ) ), a2( std::move( a2 ) )
{
std::cout << "D( A &&, A && )\n";
}
A a1, a2;
};
int main()
{
B b( A{}, A{} );
std::cout << "b.a1.x = " << b.a1.x << ", b.a2.x = " << b.a2.x << '\n';
std::cout << '\n';
C c( A{}, A{} );
std::cout << "c.a1.x = " << c.a1.x << ", c.a2.x = " << c.a2.x << '\n';
std::cout << '\n';
D d( A{}, A{} );
std::cout << "d.a1.x = " << d.a1.x << ", d.a2.x = " << d.a2.x << '\n';
std::cout << '\n';
return 0;
}
The program output is
explicit A(), x = 1
explicit A(), x = 2
A( const A & ), x = 3
A( const A & ), x = 4
B( const A &, const A & )
~A(), x = 2
~A(), x = 1
b.a1.x = 3, b.a2.x = 4
explicit A(), x = 5
explicit A(), x = 6
A( A && ), x = 7
A( A && ), x = 8
C( A, A )
~A(), x = 6
~A(), x = 5
c.a1.x = 7, c.a2.x = 8
explicit A(), x = 9
explicit A(), x = 10
A( A && ), x = 11
A( A && ), x = 12
D( A &&, A && )
~A(), x = 10
~A(), x = 9
d.a1.x = 11, d.a2.x = 12
~A(), x = 12
~A(), x = 11
~A(), x = 8
~A(), x = 7
~A(), x = 4
~A(), x = 3
It is seen that in the both programs the class D
behaves more efficiently compared with the classes B
and C
.
Upvotes: 1
Reputation: 158
Well, You made me recall a good rule of thumb one of my professors taught me once.
When you have to decide if to pass an object to a function by value or by reference: if the object is larger than the reference to it, up to 4x - we will pass the object itself, if it's larger than 4x - then we will pass a reference to it.
Of course, it's under the assumption that in either way we are not planning to change the object.
Upvotes: 0
Reputation: 238301
Is the first one more efficient,
It can be, potentially. But not necessarily.
or is the efficiency gained so trivial it doesn't matter?
It depends on many things. If the strings are short (enough to fit within the small string optimisation of the target system), then the difference will likely be small either way.
If you are not going to store a copy of the string, and if the string is large, then reference will likely be significantly faster.
If you are going to store a copy, and are passing an rvalue to the function, and the string is large, then the non-reference will likely be significantly faster because you can create the copy by moving.
Regardless, whether the difference between these alternatives is significant in your program, can be found only by measuring.
Upvotes: 1