4

I have the class MyObject. All instances of it should be owned by a MyObjectSet, and it should not be possible to construct it anywhere else. Inside MyObjectSet, I use a std::vector<MyObject> to store all instances in.

The problem is, that for std::vector<MyObject> to work, the move constructor of MyObject has to be public (it is not enough to add std::vector<MyObject> as a friend of MyObject).

class MyObject {
    MyObject(int n);
    friend class MyObjectSet;
  public:
    MyObject(MyObject&&) = default; // without this, it doesn't compile
};

class MyObjectSet {
    std::vector<MyObject> MyObjects;
  public:
    MyObject& GetMyObject(int some_param);
};

But if I make it public, it would be possible to instantiate MyObject from elsewhere.

void SomeUnrelatedFunc(MyObjectSet& set) {
    MyObject m(std::move(set.GetMyObject(0))); // this shouldn't be possible
}

Is there any way to solve this problem?

It is possible to store pointers to MyObject instances inside MyObjectSet instead, but I'd like to avoid that, if possible.

Zyx 2000
  • 1,625
  • 1
  • 12
  • 18
  • Are you also against storing smart pointers to `MyObject`? (And if so, why?) – jamesdlin Mar 30 '13 at 19:55
  • You could make the retval of GetMyObject a const reference maybe? – Etherealone Mar 30 '13 at 20:07
  • 1
    I don't see the problem in allowing a move constructor. It doesn't let you create new objects, unless you invalidate existing ones, so you're just moving resources from one instance to another. And if you disallow both copying and moving, it's going to be really hard to use your class for *anything*. – jalf Mar 30 '13 at 20:14

2 Answers2

2

You need to declare std::allocator<MyObject> as a friend:

class MyObject {

    MyObject(int n);
    friend class MyObjectSet;
    friend std::allocator<MyObject>;
};

Then use vector::emplace_back() to actually construct your objects.

class MyObjectSet {
    std::vector<MyObject> MyObjects;

public:
    MyObject& GetMyObject(int some_param)
    {
        MyObjects.emplace_back( some_param );
        return MyObjects.back();
    }
};

emplace_back() has the effect of calling the MyObject constructor, passing some_param as the int parameter to the constructor, and inserting the created object into the back of the list. The construct call is done from within std::allocator, so its friendship is required.

OldPeculier
  • 11,049
  • 13
  • 50
  • 76
  • Doesn't work either, unfortunately. It complains about the move constructor being private in the instantiation a couple of implementation-detail templates. I can't make all of them friend classes. – Zyx 2000 Mar 30 '13 at 20:53
  • @Zyx2000 Hm.. Works with Clang and libc++. I wouldn't think that GCC would be distinct in this. – OldPeculier Mar 30 '13 at 21:33
  • Ah, I see the problem. GNU uses `__gnu_cxx::new_allocator` as the name for their allocator. Okay, another answer is forthcoming... – OldPeculier Mar 30 '13 at 21:35
0

You could create a custom allocator and pass it as the second parameter to your std::vector<>. Make your custom allocator a friend of MyObject. Your custom allocator doesn't need to do anything special--the only purpose is to allocate MyObjects in the usual way (e.g. with new) while accessing the private constructor by virtue of being a friend.

OldPeculier
  • 11,049
  • 13
  • 50
  • 76