【问题标题】:Inheriting a private constructor继承私有构造函数
【发布时间】:2017-09-11 14:11:19
【问题描述】:

试图在具有私有 ctor 的类上允许 make_unique 我在两种情况下遇到了以下奇怪的区别:


案例 1 - 空 ctor - 编译

class A {
    int _i;
    A(): _i(7) {}
public:
    template<typename... T>
    static std::unique_ptr<A> create(T&&... t) {
        struct enablePrivateCtor : public A {
            using A::A;
        };
        return std::make_unique<enablePrivateCtor>(std::forward<T>(t)...);
    }
    void doIt() const {
        std::cout << _i << std::endl;
    }
};

int main() {
    auto a = A::create();
    a->doIt();
}

输出:

7

案例 2 - 非空 ctor - 无法编译

class A {
    int _i;
    A(int i): _i(i) {} // <- change 1, ctor getting int
public:
    // no change here!
    template<typename... T>
    static std::unique_ptr<A> create(T&&... t) {
        struct enablePrivateCtor : public A {
            using A::A;
        };
        return std::make_unique<enablePrivateCtor>(std::forward<T>(t)...);
    }
    void doIt() const {
        std::cout << _i << std::endl;
    }
};

int main() {
    auto a = A::create(7); // <- change 2, sending 7
    a->doIt();
}

编译错误:

unique_ptr.h: error: calling a private constructor of class 'enablePrivateCtor'

为什么第一个 - 带有空 ctor - 可以,而第二个 - 非空 ctor - 不行?

【问题讨论】:

  • 与您的问题无关,以及更多帮助您改进问题的提示:我们不需要看到A两次。相反,您可以只使用第一个示例,但是使用像 auto b = A::create(7); // Does not compile 这样的第二行可以不必显示程序正在运行或其输出,因为问题是关于构建错误(我假设)。
  • 仅供参考:这个想法是错误的。你不能像这样使用派生类;您的 create 函数调用 UB。相反,您应该依赖一个私有的“密钥”类实例,它只能由有权访问A 的人构建。您想要“私有”的 A 的构造函数应该将该类的实例作为参数。这样,那些有权访问A 的人就可以创建这样一个实例并将其传入。
  • @Someprogrammerdude:这些例子不同的。在第二个示例中,A 将参数传递给其私有构造函数。
  • 默认的 c'tor 并不是真正继承的。它是由编译器自动生成的。
  • @MooingDuck: enablePrivateCtor 派生自 A,但 A 没有虚拟析构函数。所以从基类指针中销毁它(当你将它转换为unique_ptr&lt;A&gt;时会发生这种情况将产生UB。

标签: c++ c++11 constructor


【解决方案1】:

默认构造函数永远不会被继承。因此,第一个enablePrivateCtor生成一个默认构造函数,调用基类默认构造函数。

当您继承构造函数时(如第二种情况),新构造函数与继承的构造函数具有相同的访问级别。所以由于A::A(int) 是私有的,所以enablePrivateCtor::enablePrivateCtor(int) 也是私有的。所以你将无法使用它进行构建。

如果您需要能够间接调用私有构造函数(通过make_unique/emplace/etc),那么您需要使用私钥类型。像这样:

class A;

class A_key
{
  A_key() = default;
  A_key(int) {} //Prevent `A_key` from being an aggregate.

  friend class A;
};

class A {
    int _i;
public:
    A(int i, A_key): _i(i) {}

    // no change here!
    template<typename... T>
    static std::unique_ptr<A> create(T&&... t)
    {
        return std::make_unique<A>(std::forward<T>(t)..., A_key{});
    }

    void doIt() const {
        std::cout << _i << std::endl;
    }
};

...

auto ptr = A::create(7);
A a(7, A_key{}); //Does not compile, since you're not a friend.

A_key 可公开复制,但不可公开默认构造。所以非私有代码可以传递它们,但非私有代码不能创建它们。

【讨论】:

  • 对未来读者的注意:这种访问修饰符的继承是构造函数所特有的:“命名构造函数的 using-declarator 不会创建同义词;相反,如果可以访问其他构造函数,则它们是可访问的用于构造对应基类的对象时,忽略using-declaration的可访问性。"
【解决方案2】:

不同之处在于enablePrivateCtor 自动获得一个默认构造函数( 允许调用A::A)。

它不会自动获取整数转换构造函数:添加

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

看看效果如何。

【讨论】:

  • OP 继承构造函数,因此期望不必编写它。
  • 我也很惊讶,但是用 g++7.2 快速检查一下就知道了。我发布了答案,但仍在检查这是正确的还是编译器错误。
  • @Useless 必须有更多的东西(可能是一个错误......),因为派生不应该能够访问私有 ctor:godbolt.org/g/GUGTAE
  • 我想知道是不是因为派生是 A 方法之一的本地...
  • 即,因为函数内部的本地类(包括成员函数)可以访问与封闭函数可以访问的名称相同的名称。
【解决方案3】:

您发布的代码具有未定义的行为。

特别是,您分配一个enablePrivateCtor,然后删除一个A

比这更好的方法是使用键类型。

class A {
  int _i;
  A(): _i(7) {}
  class construction_token_t {
    explicit construction_token_t(int) {}
    friend class A;
  };
  static auto ctor_token() { 
    return construction_token_t(0);
  }
public:
  template<class...Args>
  A( construction_token_t, Args&&...args ):A(std::forward<Args>(args)...){}

  template<typename... T>
  static std::unique_ptr<A> create(T&&... t) {
    return std::make_unique<A>(ctor_token(), std::forward<T>(t)...);
  }
  void doIt() const {
    std::cout << _i << std::endl;
  }
};

我们创建了一个令牌,它可以授予另一个类访问我们私有 ctor 的权限。唯一可以创建这个令牌的是我们的班级。

然后我们将它传递给make_unique


另一种方法是使用工厂 lambda 模式。

template<class F>
struct factory_lambda_t {
  F f;
  template<class T>
  operator T() const { return f(); }
};

template<class F>
factory_lambda_t<std::decay_t<F>>
factory( F&& f ) { return {std::forward<F>(f)}; }

在 C++14 中,这要求移动/复制 ctor 是公开的,但在 C++17 中则不需要。

class A {
  int _i;
  A(): _i(7) {}
public:
  template<typename... T>
  static std::unique_ptr<A> create(T&&... t) {
    return std::make_unique<A>(factory([&]{
      return A(std::forward<T>(t)...);
    }));
  }
  void doIt() const {
    std::cout << _i << std::endl;
  }
};

我认为这很漂亮。

这可能会导致在某些情况下完全省略构造函数。在其他情况下,发生单次移动。

【讨论】:

    猜你喜欢
    • 2011-10-30
    • 1970-01-01
    • 1970-01-01
    • 2014-02-03
    • 2019-08-24
    • 1970-01-01
    • 2018-12-12
    • 2013-06-01
    相关资源
    最近更新 更多