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.

Philluminati
  • 2,649
  • 2
  • 25
  • 32
  • 1
    Is there a particular reason why you wanted to use a string& instead of a plain string? – Scott Langham Feb 04 '12 at 15:45
  • possible duplicate of [Non-static const member, can't use default assignment operator](http://stackoverflow.com/questions/634662/non-static-const-member-cant-use-default-assignment-operator) – Joe Feb 04 '12 at 15:49

4 Answers4

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.

  • Ok I understand what you're saying, but I "dislike" references. Even though here I'm not required to `new` or `delete` anything, someone may accidentally assume I've forgotten to delete it, and thus add broken code to the project. However I think I'm willing to take your answer, that I do need a reseatable reference, as an indication that I should use a smart pointer. – Philluminati Feb 04 '12 at 16:04
  • Ok, now I realise I _can't_ use a smart pointer, because it would implicitly try to `delete` something that was declared on the stack. Does this mean your solution is the only valid one (unless I change the constructor signature?) – Philluminati Feb 04 '12 at 16:05
  • 1
    @Philluminati What's preventing you from just _documenting_ the whole thing to prevent others from messing with your code? – Etienne de Martel Feb 04 '12 at 16:08
  • @EtiennedeMartel That's true, I could I guess. Also I still don't think there is another solution so I have to. Also my first comment confusingly said I disliked references, when I meant "disliked pointers". Anyway, I think this answer is the only way to go. – Philluminati Feb 04 '12 at 16:11
  • 1
    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. – Kerrek SB Feb 04 '12 at 16:12
  • 2
    @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 struct non_owning_ptr { typedef T* type; }` and then use `non_owning_ptr::type my_str`. – Ben Voigt Feb 04 '12 at 16:16
  • 2
    @Kerrek: But the code should be self-documenting whenever possible. That's possibly the best argument against "raw" pointer syntax. – Ben Voigt Feb 04 '12 at 16:16
  • @BenVoigt: That's true in an ideal world, but from the OP's description of the reference being to an automatic object, it appears that her design has already moved far beyond "self-documenting", so the next-best thing is to provide a clear documentation of each class. – Kerrek SB Feb 04 '12 at 16:18
  • 1
    @Ben: `template using observing_ptr = T*;` :) – Xeo Feb 04 '12 at 16:48
  • @Xeo: I thought there was some new template typedef feature adding in C++11. Do any compilers support it yet? – Ben Voigt Feb 04 '12 at 17:41
  • @Ben: Clang does, and I think GCC 4.7 too. – Xeo Feb 04 '12 at 18:58
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;
};
moswald
  • 11,491
  • 7
  • 52
  • 78
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;
}
Lee Hunt
  • 23
  • 4
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;
}
bitmask
  • 32,434
  • 14
  • 99
  • 159
  • upvoted. Don't know why this isn't used more often to reseat references. Perfectly legal. – doug May 12 '22 at 04:10
  • @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. – bitmask May 12 '22 at 10:22
  • 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 May 12 '22 at 14:08
  • @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. – bitmask May 12 '22 at 15:42
  • There were significant restrictions for altering references and consts in a class prior to c++20. This changed in [basic.life](https://timsong-cpp.github.io/cppwp/n4868/basic.life#8) 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. – doug May 12 '22 at 16:56