c++11 pass by value vs reference

If I understand this correctly, in C++11 pass-by-value can be as or more efficient than pass-by-reference. However I was looking at some C++11 code and noticed these uses being mixed, specifically std::string would be passed as a const reference to a constructor, but a std::vector would be passed as a value to the same constructor.

MyClass(const std::string &s, std::vector<Type> v) : s(s), v(std::move (v)) {}


then the constructor is used like:

1
2
3
4
5
6
7
void some function() {
std::string text;
std::vector<Type> values;
//do some stuff
//done using text, values
MyClass object (text, std::move(values));
}


std::string :
text is passed by reference to constructor, no copy of data.
then, field s is constructed from reference, copy is made.

std::vector :
values is converted to an "r-value", pass-by-value does not copy because of C++11 magic.
then, field v is constructed from r-value by using std::move again, NO copy is made.

Is there any reason for having this difference? Does pass-by-rvalue only have benefit for heap-allocated memory, which would mean it is worse than pass-by-value for stack-allocated memory? (is std::string on the stack or the heap?)
Last edited on
in C++11 pass-by-value can be as or more efficient than pass-by-reference
I wouldn't agree with that.

That vector should be passed by const ref and the std::move removed. As it stands, copy of the vector passed to the constructor of MyClass. The content of that copy is moved to v. When MyClass::MyClass() completes, the (empty) copy is destroyed. You may as well pass by const ref and copy that reference.

You probably want to read this:
http://thbecker.net/articles/rvalue_references/section_03.html
> C++11 pass-by-value can be as or more efficient than pass-by-reference.

Yes. Where prvalues of moveable types are involved.
(With lvalues, it wouldn't make a difference; an actual copy has to be made somewhere.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <vector>

namespace 
{
    struct A
    {
        A( std::vector<int> vec ) : seq( std::move(vec) ) {}
        std::vector<int> seq ;
    };

    struct B
    {
        B( const std::vector<int>& vec ) : seq(vec) {}
        std::vector<int> seq ;
    };
    
    std::vector<int> make_vector() ;
}

std::size_t pass_by_value_and_move()
{
    return A( make_vector() ).seq.size() ;
}

std::size_t pass_by_reference_to_const_and_copy()
{
    return B( make_vector() ).seq.size() ;
}


Pass by value and move:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
clang version 3.6.0 (tags/RELEASE_360/final 235480)

	.text
	.file	"main.cpp"
	.globl	_Z22pass_by_value_and_movev
	.align	16, 0x90
	.type	_Z22pass_by_value_and_movev,@function
_Z22pass_by_value_and_movev:            # @_Z22pass_by_value_and_movev
	.cfi_startproc
# BB#0:
	pushq	%rbx
.Ltmp0:
	.cfi_def_cfa_offset 16
	subq	$32, %rsp
.Ltmp1:
	.cfi_def_cfa_offset 48
.Ltmp2:
	.cfi_offset %rbx, -16
	leaq	(%rsp), %rdi
	callq	_Z11make_vectorv
	movq	(%rsp), %rdi
	movq	8(%rsp), %rbx
	xorps	%xmm0, %xmm0
	movaps	%xmm0, (%rsp)
	movq	$0, 16(%rsp)
	subq	%rdi, %rbx
	sarq	$2, %rbx
	testq	%rdi, %rdi
	je	.LBB0_5
# BB#1:                                 # %_ZN12_GLOBAL__N_11AD2Ev.exit
	callq	_ZdlPv
	movq	(%rsp), %rdi
	testq	%rdi, %rdi
	je	.LBB0_5
# BB#2:
	movq	8(%rsp), %rax
	cmpq	%rdi, %rax
	je	.LBB0_4
# BB#3:                                 # %.lr.ph.i.i.i
	leaq	-4(%rax), %rcx
	subq	%rdi, %rcx
	notq	%rcx
	andq	$-4, %rcx
	addq	%rax, %rcx
	movq	%rcx, 8(%rsp)
.LBB0_4:                                # %_ZNSt3__113__vector_baseIiNS_9allocatorIiEEE5clearEv.exit.i
	callq	_ZdlPv
.LBB0_5:                                # %_ZNSt3__113__vector_baseIiNS_9allocatorIiEEED2Ev.exit
	movq	%rbx, %rax
	addq	$32, %rsp
	popq	%rbx
	retq
.Ltmp3:
	.size	_Z22pass_by_value_and_movev, .Ltmp3-_Z22pass_by_value_and_movev
	.cfi_endproc


	.ident	"clang version 3.6.0 (tags/RELEASE_360/final 235480)"
	.section	".note.GNU-stack","",@progbits

http://coliru.stacked-crooked.com/a/e94b32e7e0c16dbe

Pass by reference to const and copy: too big to post (295 lines),
see: http://coliru.stacked-crooked.com/a/e2dd846bb236734d
Last edited on
The reason why string was passed by reference might be in fact that strings expected to be passed are short and developers main compiler performs small string optimisation.
In this case move will perform same actions as copy and in fact will be slightly slower (move/copy twice instead of passing one pointer and copying once).

I believe it to be an unnecesary microoptimisation in this case, but cannot say for sure without profiling.
Last edited on
Thank you for the replies.

I'm still slightly confused about the whole rvalue thing.

So if there's a function like
void f (SomeType arg);

if it is called with a "prvalue" like
f(function_that_returns_SomeType() );
Then a move takes place instead of a copy.

But if it is an rvalue that isn't a "prvalue" (if I understand this correctly, that would be an "xvalue" then?) like this
f(std::move(object_of_SomeType) );
Then does a move take place instead of a copy (assuming SomeType implements the proper constructors) ?
Yes, it does. Both prvalue and xvalue are rvalues, so this applies:
An rvalue may be used to initialize an rvalue reference, in which case the lifetime of the object identified by the rvalue is extended until the scope of the reference ends.
An xvalue ("eXpiring value") is an expression that identifies an object that has identity and which can be moved from.
Only the following expressions are xvalues:
A function call or overloaded operator expression if the function's or the overloaded operator's return type is an rvalue reference to object type, such as std::move(val)
http://en.cppreference.com/w/cpp/language/value_category
move constructor is the best match for rvalue references
> But if it is an rvalue that isn't a "prvalue" (if I understand this correctly, that would be an "xvalue" then?) like this
> f(std::move(object_of_SomeType) );
> Then does a move take place instead of a copy (assuming SomeType implements the proper constructors) ?

Yes. With xvalues, copy-elision is unlikely.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>

struct A
{
    A() = default ;
    A( const A& ) { std::cout << "copy constructor" ; }
    A( A&& ) noexcept { std::cout << "move constructor" ; }
    A& operator = ( const A& ) = default ;
    A& operator = ( A&& ) noexcept = default ;
    ~A() = default ;
};

void f( A ) {}
#define CALL_F(arg) ( ( std::cout << "f( " #arg << " ): " ), f(arg), ( std::cout << '\n' ) )

A foo()  { A a ; return a ; }
const A& bar() { static A a ; return a ; }

A&& baz()
{
    static const std::size_t N = 1000 ;
    static A a[N] ;
    static std::size_t n_calls = 0 ;

    if( n_calls < N ) return std::move( a[ n_calls++ ] ) ;
    else throw "too many calls" ;
}

int main()
{
    A a ;
    CALL_F(a) ; // lvalue: copy constructor
    CALL_F( A{} ) ; // prvalue: copy elison
    CALL_F( foo() ) ; // prvalue: copy elision
    CALL_F( bar() ) ; // lvalue: copy constructor
    CALL_F( std::move(a) ) ; // xvalue: move constructor
    CALL_F( baz() ) ; // xvalue: move constructor
}

http://coliru.stacked-crooked.com/a/447b01293e49d321
Topic archived. No new replies allowed.