继承私有构造函数

时间:2017-09-11 14:11:19

标签: c++ c++11 constructor

尝试在私有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 - 不是?

3 个答案:

答案 0 :(得分:4)

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

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

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

class A;

class A_key
{
  A_key() = default;

  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是可公开复制的,但它不是公开默认的可构造的。因此非私有代码可以传递它们,但是非私有代码无法创建它们。

答案 1 :(得分:3)

区别在于enablePrivateCtor会自动获取默认构造函数( 允许调用A::A)。

它不会自动获得整数转换构造函数:add

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

看到它有效。

答案 2 :(得分:2)

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

特别是,您分配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中,这要求移动/复制ctors是公开的,但在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;
  }
};

我认为非常光滑。

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