【问题标题】:C++11 Declaring factory a friend of base classC++11 声明工厂是基类的朋友
【发布时间】:2014-07-23 17:37:12
【问题描述】:

我正在尝试为派生类创建一个工厂。我只希望工厂能够创建派生类的实例,所以我创建了基本构造函数protected;派生类只使用基类构造函数,所以它们的构造函数也是protected

我尝试将工厂声明为基类的友元,以便它可以访问protected 构造函数。当我使用这个命令编译时

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

我收到此错误:

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;
                    ^

以及派生类B 的类似错误。

我从阅读 stackoverflow.com 上的其他问题中得到感觉,这在 C++11 中是不可能的,但我不确定为什么。有人可以解释为什么这不起作用,也许还有另一种选择?

示例代码

#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;
}

【问题讨论】:

  • 不确定这是否是您的目标,但可以编译:codepad.org/2l2l1V44。使用受保护的继承和初始化而不是使用 Base::Base。
  • 这是你遇到的一个非常奇怪的错误,我的直觉告诉我它应该可以工作,但 gcc 和 clang 似乎都因为你提到的原因拒绝它。
  • 在将friend A* Create&lt;A&gt;(int); 添加到类A 和类B 中的相应代码时,也会在gcc 4.9 上编译。
  • 简单地说:“我父母的朋友不一定是我的朋友”
  • @BЈовић 我不同意这是友谊继承问题的重复。 Here's an example 没有友元函数,由于同样的原因仍然无法编译。这里的问题是继承的构造函数在派生类中保留相同的访问权限。

标签: c++ inheritance c++11 factory friend


【解决方案1】:

当您从基类继承构造函数时,无论您将using 声明放在派生类中的什么位置,它都会保留原始构造函数的访问权限。

来自§12.9/4 [class.inhctor]

这样声明的构造函数与X 中的相应构造函数具有相同的访问权限。 ...

如果您将构造函数显式添加到派生类而不是从Base 继承它们,则可以修复错误。

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

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

Live demo

另一个解决方案,当然是让Base的构造函数public。您也可以将其析构函数设为protected,但这不是必需的,因为由于纯虚成员函数,该类无论如何都无法实例化。

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

【讨论】:

  • 实际上,同时需要protectedvirtual 的情况很少见;你要么有protected 和非虚拟(Base* 上没有delete)或publicvirtualBase* 上的delete)。两者都需要的唯一情况是,如果您有一些奇怪的情况,即 friend(或孩子)调用 delete,但这似乎很奇怪。
  • 郑重声明,我不知道是谁有这样的想法,让using Base::Base 的行为与using Base::foo 不同,但这确实令人困惑(再一次)。
  • @MatthieuM。当然,protectedvirtual 是对的。我在想他的Create 函数返回一个Base*,而不是T* 当我写的时候(但是你不能删除工厂函数创建的任何东西)。同意继承构造函数的行为是不直观的,还没有想出一个很好的理由来解释为什么要以这种方式实现。
  • 顺便说一句,获得标准报价的工作做得很好:)
【解决方案2】:

友谊does not go down the inheritance tree。 Create 是 Base 的朋友,因此无法访问受保护的 A::A(int) 和 B::B(int)。

可能的解决方案包括:

  • 结交新朋友(A、B 和其他儿童班级应成为Create 的朋友)
  • 使用@Snps 提到的公共构造函数
  • 使用只有Base(因此也是它的朋友Create)可以创建而其余的只能复制的外部类。来自here的想法。

最后一个解决方案的代码:

#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;
}

【讨论】:

  • 问题是,当using Base::Basepublic 部分时,为什么A::A(int) 被认为是protected?通常,using 声明 brings in the name
  • @MatthieuM。我想知道这是否是我自己的问题,但是如果 OP 想限制 AB 的构造,那么我不明白使用 using 的公共构造函数对此有何帮助。 OP还写道“派生类只使用基类构造函数,因此它们的构造函数也是protected。”
  • 是的,我看到了这个评论,但随后 OP 评论说“理想情况下,构造函数不应在 AB 中公开。我只是将它们放在那里以使示例编译。” ,这让我想知道到底要求什么。
【解决方案3】:

我很想将Create() 函数设为 Base 的静态成员,然后将所有派生类设为 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;
}

编辑:添加了 static_assert

编辑:添加了运行代码的链接

【讨论】:

    猜你喜欢
    • 2023-03-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多