简体   繁体   English

C++ 避免调用抽象基类的构造函数

[英]C++ avoid call to abstract base class's constructor

Currently, I am building a library in C++ (using C++11 standards), and I am stuck on trying to figure out how to make my design more practical.目前,我正在用 C++(使用 C++11 标准)构建一个库,并且我一直在试图弄清楚如何使我的设计更实用。 I have the following abstract class E我有以下抽象类E

template<typename K>
class E 
{
 public:
 virtual ~E() {};
 virtual void init() = 0;
 virtual void insert(const K& k) = 0;
 virtual size_t count() const = 0;
 virtual void copy(const E<Key>& x) = 0;
};

which I want to restrict users from instantiating it (ie, be an interface).我想限制用户实例化它(即,成为一个接口)。 E has two sub-classes that implement the corresponding methods: E有两个实现相应方法的子类:

template<typename K>
class EOne : public E<K>
{
 public:
 EOne() {}
 EOne(const EOne& x) {...}
 void init() override
 {
   ...
 }
 void insert(const K& v) override
 {
   ...
 }
 size_t count() const override
 {
   ...
 }
 void copy(const E<K>& o) override
 {
   ...
 }
 private: 
     // some private fields
};

and ETwo : public E<K> , which is similar to EOne .ETwo : public E<K> ,类似于EOne Also, there is a different class J , which has a member std::vector<E<K>> that needs to be instantiated during construction:此外,还有一个不同的类J ,它有一个成员std::vector<E<K>>需要在构造过程中实例化:

template<typename K>
class J
{
 public:
     J(size_t n, const E<K>& e) : v(n, e)
     {}
 private:
     std::vector<E<K>> v;
}

In essence, by getting a constant reference for a E<K> object, I want J 's constructor to use the reference to instantiate all n objects of v using e as a template (ie, call the copy constructor).本质上,通过获取E<K>对象的常量引用,我希望J的构造函数使用该引用来实例化v所有n对象,使用e作为模板(即调用复制构造函数)。 As you can imagine, my goal is to have e be an object of either EOne<K> or ETwo<K> .可以想象,我的目标是让e成为EOne<K>ETwo<K> For instance, I would make a call to J<K>::J(size_t n, const E<K>& e) the following way:例如,我会通过以下方式调用J<K>::J(size_t n, const E<K>& e)

int main(int argc, char** argv)
{
    EOne<std::string> e;
    J<std::string> j(10, e); // populate v with 10 copies of e
    ...
}

However, the above does not compile and the compiler complains that I cannot instantiate abstract class (I am using vc++ but I believe I will get the same error on other compilers as well).但是,上面没有编译,编译器抱怨我无法实例化抽象类(我使用的是 vc++,但我相信我也会在其他编译器上得到同样的错误)。 Therefore, my question has to do on how I can overcome this problem?因此,我的问题与如何克服这个问题有关? Do you have any suggestions on how I can make my design more practical.你对我如何使我的设计更实用有什么建议吗?

Thank you谢谢

There is more than one approach to this.对此有不止一种方法。 What follows is the most complex reasonable one.接下来是最复杂的合理的。 It requires lots of work in the type definitions, but leads to the cleanest "client" code that uses these types.它需要在类型定义中进行大量工作,但会导致使用这些类型的最干净的“客户端”代码。


It is time to learn how to make a type regular.是时候学习如何使类型成为常规类型了。

An instance of a regular type behaves like a value.常规类型的实例表现得像一个值。 C++ algorithms and container work far better with regular types than it does with abstract types. C++ 算法和容器对常规类型的工作比对抽象类型的工作要好得多。

template<class K>
class E_interface {
public:
  virtual ~E_interface() {};
  virtual void init() = 0;
  virtual void insert(const K& k) = 0;
  virtual size_t count() const = 0;
  virtual void copy_from(const E_interface& x) = 0;
  std::unique_ptr<E_interface> clone() const = 0;
};

this is basically your E , except I added clone() .这基本上是你的E ,除了我添加了clone()

template<class T, class D=std::default_delete<D>, class Base=std::unique_ptr<T>>
struct clone_ptr:Base {
  using Base::Base;
  clone_ptr(Base&& b):Base(std::move(b)) {}
  clone_ptr()=default;
  clone_ptr(clone_ptr&&o)=default;
  clone_ptr(clone_ptr const& o):
    clone_ptr(
      o?clone_ptr(o->clone()):clone_ptr()
    )
  {}
  clone_ptr& operator=(clone_ptr&&o)=default;
  clone_ptr& operator=(clone_ptr const&o) {
    if (*this && o) {
      get()->copy_from(*o.get());
    } else {
      clone_ptr tmp(o);
      *this = std::move(tmp);
    }
    return *this;
  }
};

The clone_ptr is a smart pointer that is a unique_ptr that knows how to copy itself by calling clone() and copy_from on the stored object. clone_ptr是一个智能指针,它是一个unique_ptr ,它知道如何通过在存储的对象上调用clone()copy_from来复制自己。 It may have some typos.它可能有一些错别字。

Now we write our E:现在我们写我们的E:

template<class K>
class E {
  clone_ptr<E_interface<K>> pImpl;
public:
  E() = default;
  E( std::unique_ptr<E_interface<K>> in ):pImpl(std::move(in)) {}
  E( E const& )=default;
  E( E && )=default;
  E& operator=( E const& )=default;
  E& operator=( E && )=default;
  explicit operator bool() const { return (bool)pImpl; }

  void init() { if (*this) pImpl->init(); }
  void insert(const K& k) ( if (*this) pImpl->insert(k); }
  size_t count() const { if (*this) pImpl->count(); else return 0; }
};

Our E<K> is now a value type.我们的E<K>现在是一个值类型。 It can be stored in a vector , copied, moved, etc.它可以存储在vector ,复制,移动等。

How do we do EOne and ETwo ?我们如何做EOneETwo

First, take your existing EOne and ETwo and call them EOne_impl and ETwo_impl .首先,将您现有的EOneETwo称为EOne_implETwo_impl Implement a clone() function that does a return std::make_unique<EOne_impl>(*this);实现一个执行return std::make_unique<EOne_impl>(*this);clone()函数return std::make_unique<EOne_impl>(*this); for EOne_impl and similar for ETwo_impl .对于EOne_impl和类似的ETwo_impl

Then this helper:然后这个帮手:

template<class Impl, class K>
struct E_impl: E<K> {
  using E<K>::E<K>;
  E_impl() : E<K>( std::make_unique<Impl>() ) {}
  template<class...Args>
  E_impl(Args&&...args) : E<K>( std::make_unique<Impl>(std::forward<Args>(args)...) ) {}
};

gives us给我们

template<class K>
using Eone = E_impl< Eone_impl, K >;
template<class K>
using Etwo = E_impl< Etwo_impl, K >;

and I believe your J and main code starts compiling and working as-is.我相信您的Jmain代码开始按原样编译和工作。

What we just did was create value-semantics E<K> type that contains a pImpl (pointer to implementation) pointing to a pure-virtual interface that knows how to copy itself, as well as the interface we want on an E<K> .我们刚刚做的是创建值语义E<K>类型,它包含一个 pImpl(指向实现的指针),指向一个知道如何复制自身的纯虚拟接口,以及我们想要在E<K>上的接口.

We then forwarded the interface of E<K> to the E_interface<K> for each method.然后我们将E<K>的接口转发给每个方法的E_interface<K> We didn't expose copy_from or clone , as those become operator= and our copy constructor.我们没有公开copy_fromclone ,因为它们变成了operator=和我们的复制构造函数。

To implement E<K> , you first implement E_interface<K> .要实现E<K> ,首先要实现E_interface<K> Then I wrote a helper to create a derived type from E<K> that implicitly uses that implementation.然后我编写了一个助手来创建一个从E<K>隐式使用该实现的派生类型。

Note that our E<K> is almost-never empty;请注意,我们的E<K>几乎从不为空; not never-empty.不是永远空的。 This is more efficient and simpler, but can cause problems down the road.这更有效和更简单,但可能会导致问题。

E<K> becomes a polymorphic value-semantics type. E<K>成为多态值语义类型。 This is a strange beast in some senses (as many languages don't support such a type), but in other senses it behaves exactly the way you'd want it to behave.从某种意义上说,这是一种奇怪的野兽(因为许多语言不支持这种类型),但在其他意义上,它的行为与您希望的行为完全相同。


A similar solution in C# or Java would have the data stored in the vectors be fully garbage collected reference-semantics types, not value-semantics types. C# 或 Java 中的类似解决方案将存储在向量中的数据是完全垃圾收集的引用语义类型,而不是值语义类型。

This is similar to a std::vector<std::shared_ptr<E<K>> (with the note that shared pointers are not fully garbage collected).这类似于std::vector<std::shared_ptr<E<K>> (注意共享指针没有被完全垃圾收集)。 Also note that copies of the shared_ptr end up being pointing to the same object, not new copies of it.还要注意, shared_ptr副本最终指向同一个对象,而不是它的新副本。

A std::vector<value_ptr<E_interface<K>> would also be a reasonable solution and get rid of some of the gymnastics I did in my E<K> and E_impl<K> . std::vector<value_ptr<E_interface<K>>也将是一个合理的解决方案,并摆脱我在E<K>E_impl<K>所做的一些体操。 In this case, you wouldn't rename E to E_interface .在这种情况下,您不会将E重命名为E_interface You'd initialize the vector with你会初始化vector

J<std::string> j(10, std::make_unique<EOne<std::string>>(e))

or somesuch.或诸如此类。


Part of your problem is you have to ask yourself "what does it mean to copy an E<K> ".您的部分问题是您必须问自己“复制E<K>意味着什么”。 In C++ you get to answer this question yourself;在 C++ 中,您可以自己回答这个问题; depending on how you answer it, you may or may not be permitted to store it in a std::vector .根据您的回答方式,您可能会或可能不会被允许将其存储在std::vector

Since std::vector<E<K>> v;由于std::vector<E<K>> v; requires a static instantiation of class E, it will never compile (as you already noticed correctly).需要类 E 的静态实例化,它永远不会编译(正如您已经正确注意到的那样)。 To make it work should use为了使它工作应该使用

std::vector<std::shared_ptr<E<K>>> v;

instead.反而。 It can store your objects EOne and ETwo dynamically while they can be referred with a pointed of type E .它可以动态存储您的对象EOneETwo同时可以使用类型E来引用它们。 To add a new object to the vector you can use push_back:要将新对象添加到向量中,您可以使用 push_back:

v.push_back(std::make_shared<EOne<K>>{});

To cast between the types you can use dynamic and static cast functions for smart pointers eg std::dynamic_pointer_cast<>() .要在类型之间进行转换,您可以对智能指针使用动态和静态转换函数,例如std::dynamic_pointer_cast<>()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM