C++11 Declaring factory a friend of base class

2019-02-16 19:06发布

问题:

I'm trying to create a factory for derived classes. I only want the factory to be able to create instances of the derived classes so I've made the base constructor protected; the derived classes just use the base class constructors so their constructors are protected also.

I tried to declare the factory as a friend of the base class so that it could access the protected constructor. When I compile using this command

clang++ -std=c++11 -stdlib=libc++ Friends.cpp -o Friends

I get this error:

Friends.cpp:23:20: error: calling a protected constructor of class 'A'
        return new T(i);
               ^
Friends.cpp:42:16: note: in instantiation of function template specialization 'Create<A>' requested
      here
        A* a = Create<A>(1);
           ^
Friends.cpp:30:25: note: declared protected here
             using Base::Base;
                    ^

Along with a similar error for derived class B.

I get the feeling from reading other questions on stackoverflow.com, that this isn't possible in C++11, but I'm not sure why. Can someone explain why this won't work and perhaps an alternative?

Example code

#include <iostream>

using namespace std;

// Forward declaration
template<class T> T* Create(int i);

class Base {
    public:
        template<class T>
        friend T* Create(int);
        virtual void say() = 0;

    protected:
        Base(int i): i(i) { }   // This won't compile
        int i;
};

// Factory for Base class
template<class T>
T* Create(int i){
    return new T(i);
}

class A: public Base {
    public:
        using Base::Base;
        void say() { cout << "I am A and have a value of " << i << endl; }
};

class B: public Base{
    public:
        using Base::Base;
        void say() { cout << "I am B and have a value of " << i << endl; }
};

int main(){
    cout << "I'm creating A." << endl;
    A* a = Create<A>(1);
    a->say();

    cout << "I'm creating B." << endl;
    B* b = Create<B>(2);
    b->say();

    return 0;
}

回答1:

When you inherit a constructor from a base class it retains the access of the original constructor, regardless of where you place the using declaration in the derived class.

From §12.9/4 [class.inhctor]

A constructor so declared has the same access as the corresponding constructor in X. ...

You can fix the error if you explicitly add constructors to derived classes instead of inheriting them from Base.

A(int i) : Base(i) {}

and

B(int i) : Base(i) {}

Live demo

Another solution, of course, is to make Base's constructor public. You could also make its destructor protected, but it's not necessary since the class cannot be instantiated anyway due to the pure virtual member function.

class Base {
    public:
        template<class T>
        friend T* Create(int);
        virtual void say() = 0;

        Base(int i): i(i) { }   // This won't compile
        int i;
    protected:
        ~Base() {}
};

Live demo



回答2:

Friendship does not go down the inheritance tree. Create is friend of Base, and therefore can not access the protected A::A(int) and B::B(int).

Possible solutions include:

  • Make new friendships (A, B and further child classes should be friends of Create)
  • Use public constructors as mentioned by @Snps
  • Use an external class that only Base (and therefore also its friend, Create) can create and the rest can only copy. Idea from here.

Code for last solution:

#include <iostream>

using namespace std;

// Forward declaration
template<class T> T* Create(int i);

class Base {

        class AccessKey {
            friend class Base;
            AccessKey() {};
        public:
            AccessKey(const AccessKey& o) {}

        };
        static AccessKey getAccessKey() { return AccessKey(); }

    public:
        template<class T>
        friend T* Create(int);
        virtual void say() = 0;

        Base(int i, AccessKey k): i(i) { }   // This can be public as it can be called without and AccessKey object

    protected:
        int i;
};

// Factory for Base class
template<class T>
T* Create(int i){
    return new T(i, Base::getAccessKey());
}

class A: public Base {
    public:
        using Base::Base;
        void say() { cout << "I am A and have a value of " << i << endl; }
};

class B: public Base{
    public:
        using Base::Base;
        void say() { cout << "I am B and have a value of " << i << endl; }
};

int main(){
    cout << "I'm creating A." << endl;
    A* a = Create<A>(1);
    a->say();

    cout << "I'm creating B." << endl;
    B* b = Create<B>(2);
    b->say();

    return 0;
}


回答3:

I would be tempted to make the Create() function a static member of Base and then just make all the derived classes friends of Base:

Run This

#include <iostream>

using namespace std;

class Base {

public:
    virtual ~Base() {}

    // Factory for Base class

    template<class T>
    static T* Create(int i) {
        static_assert(std::is_base_of<Base, T>::value, "Needs to be derived from Base");
        return new T(i);
    }

    virtual void say() = 0;

protected:
    Base(int i): i(i) { }
    int i;
};

class A: public Base {

    friend Base; // Allow Base to construct

public:
    using Base::Base;

    void say() { cout << "I am A and have a value of " << i << endl; }
};

class B: public Base {

    friend Base; // Allow Base to construct

public:
    using Base::Base;

    void say() { cout << "I am B and have a value of " << i << endl; }
};

int main(){
    cout << "I'm creating A." << endl;
    A* a = Base::Create<A>(1);
    a->say();

    cout << "I'm creating B." << endl;
    B* b = Base::Create<B>(2);
    b->say();

    return 0;
}

EDIT: Added static_assert

EDIT: Added link to run code