0

I apologize if this has been asked, I'm not sure how one best words it and couldn't really find it.

I essentially have a class that I want to maintain a map of itself, and that list should have the only instantiations of the object.

using std::unordered_map;
class MyClass
{
    ~MyClass() {};
    MyClass() {};   // these actually contain code which operate on the classes data
    static unordered_map<Uint32, MyClass> list;
public:
    static const MyClass& GetObject(Uint32 key) {return list[key];};

};

When i compile my code it basically gives me a bunch of errors from the STL saying it's calling deleted functions and such, which makes sense because unordered_map probably uses the constructor and destructor, so I declared unordered_map a friend

friend class unordered_map<Uint32, MyClass>;

However there doesn't seem to be any fewer errors, which I speculate is due to classes used by unordered_map like pair, and hash. So my question is if there is an alternative to this. Should I just declare more things friends that appear to be giving errors in from the compiler, or is there another method?

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
bathtub
  • 426
  • 3
  • 15

2 Answers2

2

So. You're in the mood to do something annoying. So let's do it. As AlexD says, what you're missing is a public destructor. The unordered_map needs access to this (possibly through some implementation defined inner class).

So let's do that, and let's do what you should have done in the first place, which is to make a much smaller and simpler test case:

#include <unordered_map>

class MyClass {
    public:
        ~MyClass() {}

    private:
        MyClass() {}
};

int main() {
    std::unordered_map<int, MyClass> x;
    x.at(3);
    //x[3];
}

Now that compiles fine.

Now, note that I have commented out x[3]. We can't use that. That's because if 3 doesn't exist in the map, we'd call the default constructor of MyClass, which is private. And because the compiler doesn't know at compile time if that's true, it will need to ensure that it could call the constructor.


From the comments, there's a concern that you couldn't insert any objects into this map. Well, let's add a static factory method and get rid of that concern:

#include <unordered_map>
using std::unordered_map;

class MyClass {
    public:
        static MyClass factory() { return MyClass(); }
        ~MyClass() {}

    private:
        MyClass() {}
        int x;
};

int main() {
    std::unordered_map<int, MyClass> x;
    x.insert(std::make_pair(3, MyClass::factory()));
    x.emplace(4, MyClass::factory());
}
Bill Lynch
  • 80,138
  • 16
  • 128
  • 173
  • Aha, but see I want that behavior :I I'd like for unordered_map to call the default constructor as I have shown. Otherwise how am I supposed to put data in the map in the first place. – bathtub Aug 19 '14 at 01:44
  • You'd use the insert function: [std::unordered_map::insert](http://en.cppreference.com/w/cpp/container/unordered_map/insert) – Bill Lynch Aug 19 '14 at 01:45
  • Then I would need a copy constructor no? – bathtub Aug 19 '14 at 01:45
  • 1
    I've updated my answer. And there's an implicit public copy constructor. [link](http://en.cppreference.com/w/cpp/language/copy_constructor#Implicitly-declared_copy_constructor). Additionally, if we did `~MyClass() = default;`, we'd get an implicit move constructor too. – Bill Lynch Aug 19 '14 at 01:48
  • Hmm, this would WORK. But then I have to check to see if the key is already mapped as well as inserting the object if it isn't, and that behavior is already provided for me by operator[]. Thanks for pointing out the copy-constructor behavior as well, unfortunately I want these things to be private in the first place. – bathtub Aug 19 '14 at 01:52
  • 1
    You didn't really operate within the constraints of the issue. The point is that I don't want any other class able to construct a MyClass, however that static factory function as well as a move constructor would both allow functions out of MyClass's scope to construct a MyClass. My solution will be to replace operator[] with a static function I define in MyClass. Which is slightly annoying but I'll deal. – bathtub Aug 19 '14 at 02:11
1

UPDATE: T.C. kindly pointed out a couple things I'd overlooked, so this answer's done a complete about-face....

#include <iostream>
#include <unordered_map>
#include <map>
#include <cinttypes>

class MyClass
{
    typedef std::unordered_map<uint32_t, MyClass> Instances;
    friend Instances;
    friend std::pair<uint32_t, MyClass>;
    friend std::pair<const uint32_t, MyClass>;
  public:
    static const MyClass& getObject(uint32_t key) { return instances_[key] = 2 * key; }
    ~MyClass() {}
    int n() const { return n_; }
  private:
    MyClass() : n_(-1) { }
    MyClass& operator=(int n) { n_ = n; return *this; }
    int n_;
    static Instances instances_;
};

MyClass::Instances MyClass::instances_;

int main() {
    const MyClass& m20 = MyClass::getObject(20);
    const MyClass& m21 = MyClass::getObject(21);
    std::cout << m20.n() << ' ' << m21.n() << '\n';
}

Above code at ideone.com.

As per comments, the list of necessary friendship isn't documented by the Standard, so could break with new compiler versions or when porting to another compiler.

Alterantively, you can store (smart) pointers in the unordered_map.

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
  • Oh, so std::map is specifically allowed? – bathtub Aug 19 '14 at 02:47
  • @bathtub: no such luck - have clarified above. Boost has an offering though: boost::container::map - which I've read supported incomplete types but not in official docs - haven't found any confirmation yet. – Tony Delroy Aug 19 '14 at 02:58
  • I see, I appreciate the citation, I do believe I've come across this before, I forgot you cant define lists which hold objects in the scope which declares them. I think I'll go with the shared_ptr, there's really no good reason why the object needs to be on the stack. Assuming shared_ptr doesn't need the constructor. – bathtub Aug 19 '14 at 03:14
  • 1
    Just a tip for clarity in future questions - better to call them containers rather than lists, which evokes `std::list` - a doubly linked list data structure. `std::shared_ptr` and `std::unique_ptr` definitely work for this... parameters do not need to be complete. Only `std::array` and `std::string`'s optional Short String Optimisation may put things on the stack though - `vector`, `map`, `unordered_map`, `list` etc. all allocate with `new`. – Tony Delroy Aug 19 '14 at 03:55
  • 2
    Actually, I don't think the incompleteness of the type is a problem. The in-class declaration `static unordered_map list;` doesn't actually instantiate the template (because it's `static`). A later definition at namespace scope is what instantiates the template, but at that point the class is already a complete type. – T.C. Aug 19 '14 at 06:44
  • @T.C. hmmm... good point - I'd overlooked that - will update. Thanks. – Tony Delroy Aug 19 '14 at 07:12
  • @bathtub: apologies for the bum steer... T.C.'s got it right and the above code shouldn't have undefined behaviour, but won't be guaranteed to keep compiling or be portable either as explained in the answer. Cheers. – Tony Delroy Aug 19 '14 at 07:18
  • 1
    @TonyD 9.2/p2 "Within the class member-specification, the class is regarded as complete within function bodies" :) – T.C. Aug 19 '14 at 07:24
  • @T.C. I see... seems my habit of listing friends later in the class definition was frustrating that / all becomes pretty simple then. Thanks again! – Tony Delroy Aug 19 '14 at 07:29
  • Oh no I haven't check stack overflow for days. I ended up scrapping the code completely, but not after deciding to use pointers as the solution (this did the trick). – bathtub Aug 25 '14 at 04:20