【问题标题】:How to implement a copy constructor for a class with type-erased data member?如何为具有类型擦除数据成员的类实现复制构造函数?
【发布时间】:2016-02-17 06:46:10
【问题描述】:

我想要实现的目标:我想要一个类来保存我的程序的配置,类似于 boost::options,但没有boost。 应该这样使用:

auto port = Config.Get<int>(Options::Port);
Config.Set<Options::Port>(12345);

为了保持确切的值,我在Any 类中使用类型擦除模式(它是一种模式吗?)(是的,像 boost::variant/any,但没有 boostless)。所以我的课程看起来像这样:

#include <memory>
#include <map>
#include <mutex>

enum class Options {
  kListenPort = 0,
  kUdsPath,
  kConfigFile,
};

class AnyData;
class AnyDataBase;
class Any {
 public:
  template <typename T> Any(const T& any) : data_{any} {}
  Any(const Any& any) : data_{std::make_unique<AnyDataBase>(any.data_.get())} {}; // THIS IS WHERE I GOT DESPERATE
  ~Any(){}
  template <typename T> inline const T& As() const {
    if (typeid(T) != data_->type_info()){
      throw std::runtime_error("Type mismatch.");
    }else{
      return static_cast<std::unique_ptr<AnyData<T>>>(data_)->data_;
    }
  }

 private:
  struct AnyDataBase {
    virtual ~AnyDataBase(){}
    virtual const std::type_info& type_info() const = 0;
  };

  template <typename T> struct AnyData : public AnyDataBase {
    AnyData(const T& any_data) : data_{any_data} {}
    const inline std::type_info& type_info() const {
      return typeid(T);
    }
    T data_;
  };

  std::unique_ptr<AnyDataBase> data_;
};

class Option {
 private:
  Option(Any& value) : value_{value} {}
  Option() = delete; // we want the user to provide default value.
  ~Option(){};
  template <typename T> inline const T& Get() const {
    return value_.As<T>();
  }

 private:
  bool is_mandatory_;
  bool is_cmdline_;
  //TODO: add notifier
  Any value_;
};

using OptionsPair = std::pair<Options, std::shared_ptr<Option>>;
using OptionsData = std::map<Options, std::shared_ptr<Option>>;

class IConfig {
 public:
  virtual void Load(const std::string& filename) = 0;
};

class Config : public IConfig {
 public:
  Config(int argc, char** argv);
  ~Config() {};
  void Load(const std::string& filename);
  template <Options O> void Set(const Any& value);
  template <typename T> const T& Get(Options option);

 private:
  std::unique_ptr<OptionsData> data_;
  mutable std::mutex mutex_;
};

当我这样使用它时......

template <Options O> void Config::Set(const Any& value) {
  std::lock_guard<std::mutex> lock(mutex_);

  if (data_->find(O) == data_->end()) {
    data_->insert(std::pair<Options, std::shared_ptr<Option>>(O, value));
    // TODO: i don't get in why it doesn't work this way:
    data_->insert(OptionsData {O, std::make_shared<Option>(value)});
  } else {
    data_->at(O) = std::make_shared<Option>(value);
  }
}

...我需要Any 类来拥有一个复制构造函数(我希望有人能指出如何避免这种情况)。

而且,正如您从复制构造函数的评论中看到的那样,我不知道如何制作它,因为它没有模板化。而且我不知道如何在不知道值的类型的情况下创建新的unique_ptr,这是源unique_ptr 中的帮助,我想从中复制值。

错误是:

In file included from /usr/lib/gcc/x86_64-pc-linux-gnu/4.9.3/include/g++-v4/memory:81:0,
                 from Config.h:5,
                 from Config.cpp:2:
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.3/include/g++-v4/bits/unique_ptr.h: In instantiation of ‘typename std::_MakeUniq<_Tp>::__single_object std::make_unique(_Args&& ...) [with _Tp = Any::AnyDataBase; _Args = {Any::AnyDataBase*}; typename std::_MakeUniq<_Tp>::__single_object = std::unique_ptr<Any::AnyDataBase>]’:
Config.h:23:76:   required from here
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.3/include/g++-v4/bits/unique_ptr.h:765:69: error: invalid new-expression of abstract class type ‘Any::AnyDataBase’
     { return unique_ptr<_Tp>(new _Tp(std::forward<_Args>(__args)...)); }
                                                                     ^
In file included from Config.cpp:2:0:
Config.h:36:10: note:   because the following virtual functions are pure within ‘Any::AnyDataBase’:
   struct AnyDataBase {
          ^
Config.h:38:35: note:   virtual const std::type_info& Any::AnyDataBase::type_info() const
     virtual const std::type_info& type_info() const = 0;

更新: 以防万一有人觉得这个话题有趣或有用。 如果我做对了,就不能像这样简单地投射一个 unique_ptr:

static_cast<std::unique_ptr<AnyData<T>>>(data_)->data_;

迄今为止我发现的最清晰的解决方案 https://stackoverflow.com/a/21174979/2598608 生成的代码如下所示:

return static_unique_ptr_cast<AnyData<T>, AnyDataBase>(std::move(data_))->data_;

您必须使 unique_ptr 成员可变或从 Get() 方法中删除 const 限定符,因为 static_unique_cast() 从源 unique_ptr 中提取原始删除器,因此对其进行修改。

【问题讨论】:

  • 您可能/可能需要实现 Clone 方法来获取深层副本,使其成为纯虚拟的,以便在 AnyData 类中实现它(它有足够的知识来完整实施副本)。
  • 请减少您的代码。也就是说,您在不同的范围内声明了两个 AnyDataBase 类。
  • 谢谢,伙计们。 Niall - 不确定我是否做对了 - 你的意思是按照你的说法实现 Copy() ,而不是在 copy ctor 中使用它? Ulrich - 如果我弄错了,我很抱歉,但第一个只是一个前向声明。 //上班了,稍后回复,干杯!
  • 是的,没错,但它声明的类与Any 中的类不同。如果没有必要,您还没有完成提取最小示例。如果有必要,你会不必要地混淆读者。由于它也在错误消息中,因此即使它导致错误我也不会感到惊讶。因此,继续努力,问题将变得更加清晰。也许它甚至会完全消失。
  • 啊,对,我的错。稍后会尝试并更新。谢谢!

标签: c++ c++11 c++14 template-meta-programming type-erasure


【解决方案1】:

基本上Any 类需要知道如何创建已擦除类型AnyDataBase 的深层副本。因为AnyDataBase 是抽象的,所以它不能真正做到这一点。它需要AnyDataBase 的帮助才能做到这一点。

一种技术是在AnyDataBase 中实现“克隆”方法。此函数可以采用多种签名,但由于您已经在使用std::unique,因此可能最容易继续使用它,如下所示;

std::unique_ptr<AnyDataBase> clone() const;

Any 类中的示例实现;

class Any {
 public:
  template <typename T> Any(const T& any) : data_{std::make_unique<AnyData<T>>(any)} {}
  Any(const Any& any) : data_{any.data_->clone()} {}; // use the clone
  ~Any(){}
  template <typename T> inline const T& As() const {
    if (typeid(T) != data_->type_info()){
      throw std::runtime_error("Type mismatch.");
    }else{
      return static_cast<std::unique_ptr<AnyData<T>>>(data_)->data_;
    }
  }
 private:
  struct AnyDataBase {
    virtual ~AnyDataBase(){}
    virtual std::unique_ptr<AnyDataBase> clone() const = 0; // clone already as std::unique
    virtual const std::type_info& type_info() const = 0;
  };
  template <typename T> struct AnyData : public AnyDataBase {
    AnyData(const T& any_data) : data_{any_data} {}
    std::unique_ptr<AnyDataBase> clone() const override { return std::make_unique<AnyData<T>>(data_); }
    const inline std::type_info& type_info() const override { return typeid(T); }
    T data_;
  };
  std::unique_ptr<AnyDataBase> data_;
};

当尝试复制Any 类时,它会依次调用AnyDataBase 上的clone(),然后(在AnyData 中)创建data_ 成员(T 类型)的完整副本并返回所需的std::unique

Here is a sample of it.

注意:std::unique_ptr&lt;AnyDataBase&gt; clone() const override { return std::make_unique&lt;AnyData&lt;T&gt;&gt;(data_); } 按预期工作,由于an available constructor allowing the implicit conversion of the unique_ptr&lt;&gt;::pointer types,构造的unique_ptr&lt;AnyData&lt;T&gt;&gt; 转换为返回类型unique_ptr&lt;AnyDataBase&gt;


这种技术也称为virtual constructors,通常依赖于covariant return types;尽管在上面的示例中未使用协方差。可以轻松更改代码以使用 std::unique 上的协变返回。

有关此问题的更多讨论,请参阅 this answerthis one

【讨论】:

  • C++ 中的协方差仅限于内置引用和指针。智能指针不能是协变的。好的部分是无论如何在这里使它成为协变是没有意义的。
  • 没错,在链接的答案中有一些关于协变返回优于智能指针的优势的讨论,反之亦然。鉴于 OP 中的代码,智能指针是一个不错的选择。为了完整性和参考,包括协方差的注释;他也可以选择更改他的实现(虽然我不会,我更喜欢这里的智能指针)。
  • 很好的答案和非常有用的链接,谢谢。非常感谢。
  • @ScottTiger。对示例代码进行了更新,基本原则保持不变。
猜你喜欢
  • 1970-01-01
  • 2020-04-06
  • 1970-01-01
  • 2020-03-01
  • 1970-01-01
  • 2015-08-18
  • 2014-04-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多