C++ move constructor spot untold error

Good day admin
Please what could be the error in c++ code below.
The c++ syntax below is pouring out strange error.

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

using namespace std;

class Demo
{
    string* str;
    public:
    const string& content() const
    {
        return *str;
    }
    Demo(const string& x)
    {
        str = new string(x);
    }
    Demo(Demo&& x)
    {
        str = x.str;
        x.str = nullptr;
    }
};
int main()
{
    Demo demo1("Chiquado");
    Demo demo2 = Demo("Chiquado");
    cout<<demo1.content()<<" "<<demo2.content()<<endl;
    return 0;
}
> Edit & run on cpp.sh
It produces "Chiquado Chiquado" when run on cpp.sh

It doesn't produce any error messages and it does what it seems to suggest it should do.

If you're seeing errors, you need to actually post them.

The only issue I saw was trying to compile as C++98, and got a very explanatory error message.
main.cpp:18:14: warning: rvalue references are a C++11 extension [-Wc++11-extensions]
    Demo(Demo&& x)
             ^
Other issues with the code is that there is no destructor (memory leak) and no assignment operator function (although not used in the class test code - default does a shallow copy rather than the needed deep copy)...

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

using namespace std; //considered problematic and 'bad' but its legal and works. 

class Demo
{
    string* str; //pointers should be avoided unless studying pointers or no other way to do something. 
    public:
    const string& content() const
    {
        return *str; //this will fail if str is null or invalid
    }
    Demo(const string& x)
    {
        str = new string(x); 
    }
    Demo(Demo&& x)
    {
        str = x.str;
        x.str = nullptr; //this is how the above could fail... and it may leak memory as does the class itself
    }
};
int main()
{
    Demo demo1("Chiquado");
    Demo demo2 = Demo("Chiquado");
    cout<<demo1.content()<<" "<<demo2.content()<<endl;
    return 0;
}
Last edited on
Arguably, the onus is on you to not use that function on a moved-from object, just like how you can't dereference a moved-from smart pointer. But that should be properly documented. Certainly would be kinder to not have pre-conditions for calling content(), similar to how you can call c_str() on a moved-from std::string (I think).

What exactly is and isn't valid for a moved-from object is sometimes confusing for me, so maybe somebody else can say what the de facto standard should be.
Last edited on
The state of a moved-from object is valid but unspecified. See
https://devblogs.microsoft.com/oldnewthing/20201218-00/?p=104558
Last edited on
I still don't know what that means, though. For example, if it's "valid", does that mean that you're allowed to dereference a moved-from std::string's c_str()? Or does "unspecified" mean that c_str() could return any value?
Last edited on
c_str() could return any valid value. Note that for a pointer nullptr is a valid value but obviously can't be dereferenced. A returned non-nullptr valid value would be one that could be referenced or used with delete/delete[]

After something has been 'moved-from' then it's not common practice to refer to it's value. Usually a moved-from pointer is given a value of nullptr so that delete/delete[] will work ok if used.
It's worth noting that the valid-but-unspecified thing is only the case for objects in std. Take a look at n3241:
https://wg21.link/n3241

That being said, my understanding is that you can still call c_str and dereference the result, because c_str is specified to always return a NTSB as long as it is called on a valid std::string. If c_str instead returned a null or dangling pointer that would mean the std::string was invalid.

Also take a look at this this thread from earlier in the year:
https://cplusplus.com/forum/general/284021/
Last edited on
The move constructor is not invoked with the code as shown. Adding an output statement to that ctor shows that.

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

class Demo
{
   std::string* str;

public:
   const std::string& content( ) const
   {
      return *str;
   }
   Demo(const std::string& x)
   {
      str = new std::string(x);
   }
   Demo(Demo&& x)
   {
      std::cout << "\tMove ctor\n";

      str   = x.str;
      x.str = nullptr;
   }
};

int main( )
{
   Demo demo1("Chiquado");

   Demo demo2 = Demo("Chiquado");

   std::cout << demo1.content( ) << ", " << demo2.content( ) << '\n';
}

http://coliru.stacked-crooked.com/a/1ddd5e37ff376747

Why use a pointer to a std::string? That eliminates a lot of the automatic memory management a C++ string provides. A C++ string is not a C string, they require different usage and management.

Using a normal C++ string as a data member and std::move is arguably a better utilization of C++.
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>
#include <string>
#include <utility>

class Demo
{
   std::string str;

public:
   const std::string& content( ) const
   {
      return str;
   }
   Demo(const std::string& x)
   {
      str = x;
   }
   Demo(Demo&& x) noexcept
   {
      std::cout << "\tMove ctor\n";

      str = std::move(x.str);
      x.str.clear();
   }
};

int main( )
{
   Demo demo1("Chiquado");

   Demo demo2 = Demo("Chiquado");

   std::cout << demo1.content( ) << ", " << demo2.content( ) << '\n';

   Demo demo3 = std::move(demo2);

   std::cout << demo1.content( ) << ", " << demo2.content( ) << ", " << demo3.content( ) << '\n';
}

http://coliru.stacked-crooked.com/a/69943d80fc534448

MSVC++ whinges the move ctor should be marked noexcept, the ctor shouldn't throw exceptions.

MSVC++ also whinges about using a moved object (demo2) at line 37 as well. An illustration of the "valid but unspecified" nature of moved objects.

Note that there is no requirement for different compilers to return the same valid-but-unspecified value! My advice is don't use (except for maybe curiosity) after a moved-from.
Personally I'd treat a moved object to be as safe as an empty elevator shaft on the 40th floor with the doors open. Oooops, if you should absently walk through the doorway.

At the very least MSVC++ flags the use of a moved object as a possible issue.
Topic archived. No new replies allowed.