wrapping a class around a temporary (prvalue)

I want to define a wrapper class (BoxB) that takes another class (BoxA) as an argument by reference and stores the reference. This is not too complicated as long as the argument is an lvalue. However I would like a constructor to accept also a prvalue, esp. a temporary value, whose lifetime would somehow be extended to match that of the container. It would look like this. Is it possible?
1
2
3
4
5
6
class boxB{
    public:
    BoxA & myBox;
    boxB(BoxA  & b):myBox(b){}
    boxB(BoxA  && b):myBox( ... ){} // .... would magically make b live as long as the object
};

It’ve tried passing the prvalue as a const refer boxB(BoxA & a) but the lifetime of the object ends at the end of the statement (as I can see when tracing calls to the destructor of boxA).

I thought then that the solution could be to write an alternate constructor taking a prvalue reference as an argument boxB(BoxA && b) and to move the object during initialization. This is what the code hereunder is attempting, but I can see it is not working because the container (boxB) is initialized with a fresh copy of the argument, not a moved copy.

But even if it is possible to move the temporary object into a permanent member value, there would still be an issue because the destructor of that temporary would still be called at the end of the statement, which would make a mess.

Also, in the code, I attempt to move the prvalue into a non-reference member variable. Ideally, it should be into a new object, and the member variable should be a reference or a pointer, which would be deleted at destruction.

Finally, what ultimatly want is a templated wrapper class and the prvalue argument would be supplied first to a factory function.

Please note that BoxA and BoxB inherits from a class Box, just because I want to implement an instance counter without overloading the default constructor of BoxA (I assume there is a default move constructor)

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
#include <iostream>


int count = 0;

class box{   //box is just there to implement the counter facility without changing the default constructors of BoxA
    public:
    int num;  
    box():num(++count){std::cout << "created "<<num << std::endl;}
    ~box(){std::cout << num<<" is dead" << std::endl;}
};

class boxA:public box{
    public:
};

class boxB:public box{
    public:
    boxA & myA;
    boxA altA;  ///ideally this should be a reference or a pointer. 
    boxB(boxA & Aref):myA(Aref){std::cout <<"&->"<<myA.num << std::endl;}
    boxB(boxA && Aref):altA(std::move(Aref)),myA(altA){myA.num+=100;std::cout <<"&&->"<<myA.num << std::endl;}
};

int main()
{

boxB y(boxA{});

std::cout << "****"<<y.myA.num<<"****" << std::endl;


  return 0;
}


created 1                                                                                                                                   
created 2                                                                                                                                   
&&->101                                                                                                                                     
1 is dead                                                                                                                                   
****101****                                                                                                                                 
101 is dead                                                                                                                                 
2 is dead


The line
1 is dead 
shows that the temporary is destructed and still contains the original counter value, so what was changed to 101 is a fresh copy not a move
Last edited on
Hi,

The other day, I should have given this link as well:

https://en.cppreference.com/w/cpp/language/reference

So:

A rvalue reference with an identifier is a lvalue ;

Edit:
A rvalue reference is not necessarily a prvalue .

There also seems to be some confusion about temporaries: are you using c++17, and did you read the part about guaranteed copy elision?

I thought then that the solution could be to write an alternate constructor taking a prvalue reference as an argument boxB(BoxA && b) and to move the object during initialization.


Even if you meant rvalue reference, why do you want to do this? BoxA && b is a lvalue.

Line 22 is a move constructor, is there a reason why you don't want to specify the default move ctor?

But even if it is possible to move the temporary object into a permanent member value, there would still be an issue because the destructor of that temporary would still be called at the end of the statement, which would make a mess.


I don't understand that, b is not a temporary. Guaranteed Copy Elision means that there is no temporary. Moving means the original object is left in a consistent state, not destructed. Eg a moved std::string might be empty.

A style point: write the reference or pointer next to the type, as in:

boxB(BoxA& b)

This is written up in isocpp superfaq because in C++ it's nice to associate the reference or pointer with the type, rather than the identifier or in the middle like you have it. It's a preference thing, but that is the reason.
Last edited on
> However I would like a constructor to accept also a prvalue, esp. a temporary value, whose lifetime
> would somehow be extended to match that of the container. It would look like this. Is it possible?

No.

In general, the lifetime of a temporary cannot be further extended by "passing it on": a second reference, initialized from the reference to which the temporary was bound, does not affect its lifetime.
https://en.cppreference.com/w/cpp/language/reference_initialization#Lifetime_of_a_temporary
> However I would like a constructor to accept also a prvalue, esp. a temporary value, whose lifetime
> would somehow be extended to match that of the container. It would look like this. Is it possible?

This sounds like thinking coming from a reference-counting garbage-collected language, where a variable isn't destroyed until the last reference to it goes out of scope.
Thanks for your response. I think I might not have been crips as to the problem I am trying to solve.

BoxA && b is a lvalue.
and
b is not a temporary

I agree that BoxA && b is an lvalue and not a temporary but b exists only within the scope of the constructor, and the problem is outside the constructor.

You'll find the problem in the main program. In the expression boxB y(boxA{});, boxA{} is an prvalue and it results in a temporary object. When the evaluation of the outer expression is completed, the temporary is deleted, and the reference to the BoxA object that BoxB contains is hanging. So my question is: is there any magic that I can use to extend the life of the BoxA in the main program to match the lifetime of the boxB that refers to it; other than making a fresh copy.

Moving means the original object is left in a consistent state, not destructed

My concern is not that a potential destruction triggered by the move but one happing in the main code once the expression enclosing the temporary is completed. I don't see how it is avoidable. It seems to me that even if the temp object were somehow copy-moved by the constructor, a subsequent delete when the constructor returns could mess it up.

Line 22 is a move constructor

Actually, it is a regular constructor. It creates a boxB from a boxA,

it's nice to associate the reference or pointer with the type

I have noticed it is the standard but I am stuck with the mental model of passing an argument by reference common to many other languages rather than that of passing a reference type. Also once you've have passed it the reference type behaves like the non-reference one, so it is hard to see them as different.
Last edited on
So my question is: is there any magic that I can use to extend the life of the BoxA in the main program to match the lifetime of the boxB that refers to it

No. There is no way to transitively extend the lifetime of a temporary object.

Probably, what you want to do is move from it. Once resources are stolen (i.e., "moved from") an object, the destruction of that object should not clobber the resources it doesn't have any more.
1
2
3
4
5
6
7
8
9
10
11
12
13
int main()
{
  auto p0 = std::make_unique<int>(24);
  assert(p0); // p0 is non-null (points to a real object)

  auto p1 = std::move(p0); // move the resource from p0 into p1
  assert(p1); // p1 points to a real object
  assert(! p0); // p0 has been moved from and is now empty

  // destruction of p1 happens first - frees the "heap memory" it stole from p0
  // destruction of p0 happens next - doesn't have a resource to free anymore 
  // OK: no double free
}

Last edited on
@JLBorges

Thanks for weighing in. I am familiar with
the lifetime of a temporary cannot be further extended by "passing it on" ...
this is exactly why i am looking for a workaround.

As @mbozzi suggests the solution would seem to be to move the temp value into a member of the class during initialization. This is exactly what I have attempted in the code above but it does not seem to be working.

The following is a simplified version of the original piece of code. The output shows that there are two destructions of two distinct objects. This indicates that the temp object is not moved into the member variable.
So my question: Is there a way to make the move happen?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

#include <iostream>

struct cont{
    int a = 0;
    cont()=default;
    cont(int x):a(x){};
    ~cont(){std::cout << "destroying "<<a << std::endl;}
};

class box{
    public:
    cont & C1;
    cont C2;
    box(cont&& Cref):C2(std::move(Cref)),C1(C2){C1.a=2;}
    box(cont& Cref):C1(Cref){C1.a = 3;}
};

int main(){
    box y {cont(1)};
    std::cout << " -> "<< y.C1.a << std::endl;
}


destroying 1                   //this the temp object being destroyed  (it was not moved)                                                                                                               
 -> 2                                                                                                                                          
destroying 2                   //this box.C1 being destroyed. 


@mobozzi: as for the issue of the destructor. Your example deals with objects referred to by pointers within a single scope. That situation is very different from that of references across two scopes. What I am wondering is: how would the calling code know anything about a move in the constructor? The constructor might be in a completely different compilation unit. And because we are dealing with references and not pointers, there is no possibility to nullify the reference. So, unless I am missing something, there would be no way to prevent the reference to continue to point to the object even after it is moved and no way to prevent the destructor from being called (this might be the very reason why the std:move in the code above is not supposed moving the object!)
Last edited on
Something like this, perhaps:

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
60
#include <iostream>
#include <optional>

struct cont {

    int a = 0;
    bool moved_from = false ;

    explicit cont( int x = 0 ) noexcept : a(x) {

        std::cout << "construct " << a << " @" << this << '\n' ;
    }

    ~cont() {

        if( !moved_from ) // noisy only if it not a moved from object
            std::cout << "destroying " << a << " @" << this << '\n' ;
    }

    cont( const cont& ) = default ;

    cont( cont&& that ) : a(that.a) {

        that.moved_from = true ;
        std::cout << "moving cont from " << std::addressof(that)
                  << " to " << this << '\n' ;
    }

    // etc.
};

struct box
{
    std::optional<cont> c1 ;
    cont& c ;
    
    // move temporary object cc into the optional, initialise c to refer to the object in c1   
    explicit box( cont&& cc ) : c1( std::move(cc) ), c(*c1) {}

    // leave the optional empty, initialise c to refer to cc 
    // (making sure that the object referred to by cc outlives this box object 
    // is the responsibility of the user)
    explicit box( cont& cc ) : c(cc) {}
};

int main()
{
    box b1{ cont(23) } ; // copy temporary (rvalue)

    std::cout << "------------------\n" ;

    cont c(-99) ;
    box b2(c) ; //

    std::cout << "------------------\n" ;

    std::cout << b1.c.a << ' ' << b2.c.a << '\n' ; // 23 -99

    std::cout << "------------------\nabout to destroy b2, c and b1\n" ;
}

http://coliru.stacked-crooked.com/a/53c4ecc95334c8cc
@JLBorges

Thanks a lot for taking the time to show me in details. If you have a few minutes, I have follow-up questions:

1. The move occurs with the initialize a(that.a), correct? So it is possible to selectively move some members and not others (imagine there is another member int b, but we initialize it with b(0)).

2. There is indeed no destruction of the temporary but if I remove std::move from the class definition, there is destruction. How can the main code "know" there is a std::move in in the class constructor? It could be two completely different compilation units, and the declaration file wouldn't give any hint. Does it mean there is some sort of run-time flag to indicate a move?

3. Finally, how do I know the object was really moved (and not simply copied)? First the data is now at a different address, and also there are two independent copies at the old and new locations as the lines I added to your code shows.

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
60
61
62
63
64
65
66
67
68
#include <iostream>
#include <optional>

struct cont {

    int a = 0;
    bool moved_from = false ;

    explicit cont( int x = 0 ) noexcept : a(x) {

        std::cout << "construct " << a << " @" << this << '\n' ;
    }

    ~cont() {

        if( !moved_from ) // noisy only if it not a moved from object
            std::cout << "destroying " << a << " @" << this << '\n' ;
    }

    cont( const cont& ) = default ;

    cont( cont&& that ) : a(that.a) {

        that.moved_from = true ;
        std::cout << "moving cont from " << std::addressof(that)
                  << " to " << this << '\n' ;
        std::cout <<"before change: new  "<< a << std::endl;
        std::cout <<"beifre change: old "<<that.a << std::endl;

        a = 77;
        that.a = 88;
        std::cout <<"after change: new  "<< a << std::endl;
        std::cout <<"after change: old "<<that.a << std::endl;
    }

    // etc.
};

struct box
{
    std::optional<cont> c1 ;
    cont& c ;
    
    // move temporary object cc into the optional, initialise c to refer to the object in c1   
    explicit box( cont&& cc ) : c1( std::move(cc) ), c(*c1) {}
    // leave the optional empty, initialise c to refer to cc 
    // (making sure that the object referred to by cc outlives this box object 
    // is the responsibility of the user)
    explicit box( cont& cc ) : c(cc) {}
};

int main()
{
    box b1{ cont(23) } ; // copy temporary (rvalue)

    std::cout << "------------------\n" ;

    cont c(-99) ;
    box b2(c) ; //

    std::cout << "------------------\n" ;

    std::cout << b1.c.a << ' ' << b2.c.a << '\n' ; // 23 -99

    std::cout << "------------------\nabout to destroy b2, c and b1\n" ;
}

Last edited on
> The move occurs with the initialize a(that.a), correct?

Technically, for the member a, yes.
Note that the type of a is int; there is no difference between copy and move.
If a was of type std::string, to move the string we would write a( std::move(that.a) )


> So it is possible to selectively move some members and not others

Yes. For example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <fstream>
#include <string>

struct A
{
    // ...
    A( const std::string& s, std::ifstream file_stm )
        : str(s), // copy the string
          file( std::move(file_stm) ), // move file stream
          int_val(23) {} // and initialise the int with 23
    // ...

    std::string str ;
    std::ifstream file ;
    int int_val ;
};



> There is indeed no destruction of the temporary

There is destruction of the temprary; but the destructor does nothing and is silent.


> How can the main code "know" there is a std::move in in the class constructor?

The user of the class has no business knowing if there is a move in the constructor.
All that the user should be concerned about is that if std::move(x) is passed to a function, the object might be in a moved from state when the function returns; don't try to use x after that. The only guarantee would be that x is safely destructible.


> Finally, how do I know the object was really moved (and not simply copied)?

For a copyable and moveable class that you have authored, a check if its move constructor was invoked would give the answer. For other classes, you shouldn't really care:
For example:
1
2
3
4
5
6
7
8
9
10
11
12
13
template < typename T > struct A
{
    // ...
    explicit A( const T& v ) : value(v) {} // copy v
    
    explicit A( T&& v ) : value( std::move(v) ) {} // move construct if T has a move constructor,
                                                   // otherwise copy construct if T is copyable,
                                                   // error if neither is possible 

    // ...

    T value ;
};
Topic archived. No new replies allowed.