3

I want to have an object that contains a reference, and put that object into a vector...

Must I use smart pointers instead of a member references in any object I want to push into a vector? This was what I wanted to do:

#include <string>
#include <vector>
using namespace std;

class MyClass {
   public:
    MyClass(const string& str_ref);    //constructor
        MyClass(const MyClass& mc);        //copy constructor
   private:
        string& my_str;
};

MyClass::MyClass(const string& str_ref) :
    my_str(str_ref)
{}

MyClass::MyClass(const MyClass& mc) :
    my_str(mc.my_str)
{}


int main() {

    //create obj and pass in reference
    string s = "hello";
    MyClass my_cls(s);

    //put into vector
    vector<MyClass> vec;
    vec.push_back(my_cls);

    return 0;
}

//Throws Error
//ref.cpp:6:7: error: non-static reference member ‘std::string& MyClass::my_str’, can’t use default assignment operator

However it says I need to implement my own operator=() as the default generated one isn't valid but of course, there is no legal way to do so...

#include <string>
#include <vector>
using namespace std;

class MyClass {
   public:
    MyClass(const string& str_ref);    //constructor
        MyClass(const MyClass& mc);        //copy constructor

        MyClass operator=(const MyClass& mc);      //operator =

   private:
        string& my_str;
};

MyClass::MyClass(const string& str_ref) :
    my_str(str_ref)
{}

MyClass::MyClass(const MyClass& mc) :
    my_str(mc.my_str)
{}

//not a constructor. should not construct new object
//and return that?
MyClass MyClass::operator=(const MyClass& mc) {
   if (this != &mc) {                 //test for self-assignment.
    my_str(mc.my_str);            //can't reseat refs. this shouldn't work.
   }

   return *this;
}

int main() {

    //create obj and pass in reference
    string s = "hello";
    MyClass my_cls(s);

    //put into vector
    vector<MyClass> vec;
    vec.push_back(my_cls);

    return 0;
}

//THROWS:
//ref2.cpp: In constructor ‘MyClass::MyClass(const string&)’:
//ref2.cpp:18:19: error: invalid initialization of reference of type ‘std::string& {aka //std::basic_string<char>&}’ from expression of type ‘const string {aka const //std::basic_string<char>}’
//ref2.cpp: In member function ‘MyClass MyClass::operator=(const MyClass&)’:
//ref2.cpp:29:18: error: no match for call to ‘(std::string {aka std::basic_string<char>}) //(std::string&)’

So am I forced to use a smart pointer here or anything other than a reference?

EDIT: This is a simplification. String& is not the object being passed, it's a more complex object itself containing a vector object.

2

4 Answers 4

6

You can store a raw pointer instead of a reference here. Raw pointers can be reseated, and so they're a good way to emulate reseatable references in C++.

class MyClass
{
public:
  MyClass(const string& str_ref);
  MyClass(const MyClass& mc);
  // by the way, operator= should return a reference
  MyClass& operator=(const MyClass& mc);
private:
  string* my_str;
};

This way, operator= will be a cinch to implement.

Sign up to request clarification or add additional context in comments.

11 Comments

@Philluminati What's preventing you from just documenting the whole thing to prevent others from messing with your code?
Reference or pointer, nothing saves you from having to document the ownership semantics that your class imposes on the referred-to object. The implementation is a technical detail.
@Philluminati: In a codebase that uses smart pointers, seeing a raw pointer doesn't mean "omg he might forget to delete this", it means this is a temporary non-owning pointer. Of course you could make a "smart" pointer to make the lack of ownership explicit, how about template<typename T> struct non_owning_ptr { typedef T* type; } and then use non_owning_ptr<const string>::type my_str.
@Kerrek: But the code should be self-documenting whenever possible. That's possibly the best argument against "raw" pointer syntax.
@Ben: template<class T> using observing_ptr = T*; :)
|
5

How about using std::reference_wrapper<T>? Now you're not forced to refactor your code to allow a smart pointer, but you're also not using an internal pointer that someone may come along later and think they're supposed to delete.

class MyClass 
{
   public:
      MyClass(string &str_ref)
         : my_str(std::ref(str_ref))
      {
      }

   private:
      std::reference_wrapper<std::string> my_str;
};

1 Comment

I actually really like this answer, I'd never heard of std::reference_wrapper. Thank you.
2

One caveat. Please be sure to check for self-assignment:

MyClass& MyClass::operator=(MyClass const& from) {
  if (this != &from) {
    this->~MyClass();
    new(this) MyClass(from);
  }
  return *this;
}

Comments

1

If you want to be able to still use the assignment operator of your member (i.e. std::string& operator=(std::string const&)) you cannot use the otherwise excellent suggestion of std::reference_wrapper. But you can rebuild your object from scratch using the copy constructor so your member may actually be a raw reference:

MyClass& MyClass::operator=(MyClass const& from) {
  this->~MyClass();
  new(this) MyClass(from);
  return *this;
}

5 Comments

upvoted. Don't know why this isn't used more often to reseat references. Perfectly legal.
@doug This is a very old answer. I wouldn't recommend actually doing this today, because I'm pretty sure it is not legal. Well, the code seen there is legal, yes, but given that others are guaranteed to hold pointers or references to the old object that they intend to use, this will lead to undefined behaviour.
Perfectly legal to define your own assignment operator and reseat references in the process. At least as of c++20. They can even be changed to refer to different objects of the same type. But legal doesn't mean good practice. One needs a really compelling reason to do this and then it should be clearly documented.
@doug As far as I understand the C++ object model, the call to the destructor means that all pointers and references to the old object are no longer valid. It doesn't matter that you construct a new value in place. It's a new object.
There were significant restrictions for altering references and consts in a class prior to c++20. This changed in basic.life Both can now be changed. Also, one doesn't need to destruct an object if it doesn't use dynamic memory like strings, vectors, etc. Just use placement new or the new function std::construct_at However, references and consts that are "complete types" remain inviolate.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.