如何为具有类型擦除数据成员的 class 实现复制构造函数?
How to implement a copy constructor for a class with type-erased data member?
我想要实现的目标:我想要一个 class 来保存我的程序的配置,类似于 boost::options,但没有提升。
应该这样使用:
auto port = Config.Get<int>(Options::Port);
Config.Set<Options::Port>(12345);
为了保持准确的值,我在 Any
class 中使用类型擦除模式(它是一种模式吗?)(是的,就像 boost::variant/any,但没有提升)。所以我的 classes 看起来像这样:
#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
class 有一个复制构造函数(我希望有人能指出我如何避免这种情况)。
而且,正如您从复制构造函数的评论中看到的那样,我不知道如何制作它,因为它不是模板化的。而且我不知道如何在不知道值类型的情况下创建一个新的 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_;
迄今为止我找到的最清晰的解决方案
结果代码如下所示:
return static_unique_ptr_cast<AnyData<T>, AnyDataBase>(std::move(data_))->data_;
并且您必须使 unique_ptr 成员可变或从 Get() 方法中删除 const 限定符,因为 static_unique_cast<>() 从源中提取原始删除器 unique_ptr,因此对其进行修改。
本质上 Any
class 需要知道如何创建已擦除类型的深层副本 AnyDataBase
。它不能真正做到这一点,因为 AnyDataBase
是抽象的。它需要 AnyDataBase
的帮助才能做到这一点。
一种技术是在AnyDataBase
中实现"clone" 方法。此函数可以采用各种签名,但由于您已经在使用 std::unique
,可能最简单的方法是继续使用它,如下所示;
std::unique_ptr<AnyDataBase> clone() const;
Any
class;
中的示例实现
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
class 时,它依次调用 AnyDataBase
上的 clone()
,然后(在 AnyData
中)创建一个完整的data_
成员(T
类型)的副本和 return 所需的 std::unique
.
注意:std::unique_ptr<AnyDataBase> clone() const override { return std::make_unique<AnyData<T>>(data_); }
按预期工作,由于 an available constructor allowing the implicit conversion of the unique_ptr<>::pointer
types.[=35,构造的 unique_ptr<AnyData<T>>
转换为 return 类型 unique_ptr<AnyDataBase>
=]
此技术也称为 virtual constructors, and usually relies on covariant return types;尽管上面的示例中没有使用协方差。代码可以很容易地更改为在 std::unique
.
上使用协变 return
有关此问题的更多讨论,请参阅 this answer and this one。
我想要实现的目标:我想要一个 class 来保存我的程序的配置,类似于 boost::options,但没有提升。 应该这样使用:
auto port = Config.Get<int>(Options::Port);
Config.Set<Options::Port>(12345);
为了保持准确的值,我在 Any
class 中使用类型擦除模式(它是一种模式吗?)(是的,就像 boost::variant/any,但没有提升)。所以我的 classes 看起来像这样:
#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
class 有一个复制构造函数(我希望有人能指出我如何避免这种情况)。
而且,正如您从复制构造函数的评论中看到的那样,我不知道如何制作它,因为它不是模板化的。而且我不知道如何在不知道值类型的情况下创建一个新的 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_;
迄今为止我找到的最清晰的解决方案 结果代码如下所示:
return static_unique_ptr_cast<AnyData<T>, AnyDataBase>(std::move(data_))->data_;
并且您必须使 unique_ptr 成员可变或从 Get() 方法中删除 const 限定符,因为 static_unique_cast<>() 从源中提取原始删除器 unique_ptr,因此对其进行修改。
本质上 Any
class 需要知道如何创建已擦除类型的深层副本 AnyDataBase
。它不能真正做到这一点,因为 AnyDataBase
是抽象的。它需要 AnyDataBase
的帮助才能做到这一点。
一种技术是在AnyDataBase
中实现"clone" 方法。此函数可以采用各种签名,但由于您已经在使用 std::unique
,可能最简单的方法是继续使用它,如下所示;
std::unique_ptr<AnyDataBase> clone() const;
Any
class;
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
class 时,它依次调用 AnyDataBase
上的 clone()
,然后(在 AnyData
中)创建一个完整的data_
成员(T
类型)的副本和 return 所需的 std::unique
.
注意:std::unique_ptr<AnyDataBase> clone() const override { return std::make_unique<AnyData<T>>(data_); }
按预期工作,由于 an available constructor allowing the implicit conversion of the unique_ptr<>::pointer
types.[=35,构造的 unique_ptr<AnyData<T>>
转换为 return 类型 unique_ptr<AnyDataBase>
=]
此技术也称为 virtual constructors, and usually relies on covariant return types;尽管上面的示例中没有使用协方差。代码可以很容易地更改为在 std::unique
.
有关此问题的更多讨论,请参阅 this answer and this one。