std::map and private constructor

2020-08-09 04:41发布

问题:

Here's a code that doesn't compile:

#include <map>
using namespace std;

class A;

class B {
        friend class A;
        int b;
        B():b(1){};
        B(int b_):b(b_){};
};

class A {
        map<int,B> objects;
public:
        void init(){
              objects[2]=B(3);
        }
};

int main(){
        A a;
        a.init();
        return 0;
}

From what I understand in the error message:

/usr/include/c++/4.8/bits/stl_map.h: In instantiation of ‘std::map<_Key, _Tp, _Compare, _Alloc>::mapped_type& std::map<_Key, _Tp, _Compare, _Alloc>::operator[](const key_type&) [with _Key = int; _Tp = B; _Compare = std::less<int>; _Alloc = std::allocator<std::pair<const int, B> >; std::map<_Key, _Tp, _Compare, _Alloc>::mapped_type = B; std::map<_Key, _Tp, _Compare, _Alloc>::key_type = int]’:
foo.cc:18:24:   required from here
foo.cc:9:10: error: ‘B::B()’ is private
          B():b(1){};
          ^
In file included from /usr/include/c++/4.8/map:61:0,
                 from foo.cc:1:
/usr/include/c++/4.8/bits/stl_map.h:469:59: error: within this context
           __i = insert(__i, value_type(__k, mapped_type()));
                                                           ^

the problem is "map" is not a friend of B, so it may not use constructor B() (incidentally I notice that objects[2]=B(3); requires B()!).

I found the following workaround:

objects.insert(pair<int,B>(2,B(3)));

which works... until ~B() is also private.

So, is there a way to build a map of B inside A when B's constructors and destructors are private?

Additional question: why is objects[2]=B(3); using B()?

回答1:

Strictly speaking, it is not an answer to your question, but I think it solves your problem in a more elegant way.

Instead of making all the fields of B private and then messing with friend keyword in A, you might hide class B from outside world, by making it a private member class:

#include <map>

class A {
    class B {
        int b;

       public:
        B() : b(1){};
        B(int b_) : b(b_){};
        ~B() {}
    };
    std::map<int, B> objects;

   public:
    void init() {
        auto b = B();
        objects[2] = B(3);
        objects.insert(std::pair<int, B>(2, B(3)));
    }
};

int main() {
    A a;
    A::B b; // 'A::B': cannot access private class declared in class 'A'
    a.init();
    return 0;
}

This way you still cannot construct B freely outside of the A, but A can use it in any way. This also unlocks finer granularity of encapsulation in B: some parts can be made private/protected from A. It is not the case with friend class code.

Schematically, you can think of your code as:

 A [ B [ private(a), private(b), private(c), ... ] ]

While my code is more like:

 A[ private(B[a, b, c]) ]

i.e. privateness is "factored out" of B



回答2:

Take a look at this link http://www.cplusplus.com/reference/map/map/operator[]/ , it says

A call to this function is equivalent to:

(*((this->insert(make_pair(k,mapped_type()))).first)).second

The default constructor is being called from within the class std::map's scope, which is not a friend of this class. Therefore the error.

The error should be there. Such a constructor should not be declared private then.

Another way to go about solving this issue is to delegate construction of the objects to the class A, and have a map of integers to pointers (or reference wrappers) to B. This map should then only be accessible through an interface that A exposes. That way the entire lifetime of the B objects is managed by A and no invariants are broken.



标签: c++