有没有办法同时将一个类型分配给C++中的多个模板?
Is there a way to simultaneously assign a type to multiple templates in C++?
这个问题基于下面的示例代码,该代码的灵感来自Sean Parent的演讲。 以下代码的目标是提供一个类似于 boost::any 的对象包装器。我写这段代码是为了教育自己类型擦除。因此,此代码没有实际用途(考虑到已经有 boost::any(。
class ObjWrap {
public:
template <typename T>
ObjWrap(T O) : Self(new Obj<T>(std::move(O))) {}
template <typename T>
friend typename T * getObjPtr(ObjWrap O) {
return static_cast<T*>(O.Self->getObjPtr_());
}
private:
struct Concept {
virtual ~Concept() = 0;
virtual void* getObjPtr_() = 0;
};
template <typename T>
struct Obj : Concept {
Obj(T O) : Data(std::move(O)) {}
void* getObjPtr_() { return static_cast<void*>(&Data); }
T Data;
};
std::unique_ptr<Concept> Self;
};
在我真正提出我的问题之前,让我们从以下几个方面检查代码:
Concept::getObjPtr_
返回void*
,因为 a(Concept
不能成为模板,否则unique_ptr<Concept> Self
不起作用; b(void*
是我知道如何在C++中以与类型无关的方式返回Obj::Data
的唯一方法。如果这是错误的,请纠正我...T * getObjPtr(ObjWrap O)
是一个需要独立于ObjWrap
构造函数进行实例化的模板。ObjWrap
的使用基本上包括:a(对现有对象进行新ObjWrap
;b(在给定ObjWrap
的情况下检索底层对象。例如:ObjWrap a(1); ObjWrap b(std::string("b")); int* p_a = getObjPtr<int>(a); std::string* p_b = getObjPtr<std::string>(b);
这有效,但很明显getObjPtr<int>(b)
没有按预期工作。
所以,我的问题是:
有没有办法修复上面的代码,以便我们可以简单地使用int* p_a = getObjPtr(a)
和std::string* p_b = getObjPtr(b)
或更好的auto p_a = getObjPtr(a)
和auto p_b = getObjPtr(b)
?换句话说,有没有办法C++同时实例化两个模板(如果是这样,我们可以在编译ObjWrap
对象时实例化ObjWrap
构造函数和T* getObjPtr(ObjWrap)
,例如ObjWrap a(1)
(?
编辑 1:
使 ObjWrap 成为模板化类无济于事,因为它违背了类型擦除的目的。
template <typename T>
class ObjWrap {
/* ... */
};
ObjWrap<int> a(1); // this is no good for type erasure.
编辑 2:
我正在阅读代码并意识到可以对其进行修改以更好地反映该想法。因此,请同时查看以下代码:
class ObjWrap {
public:
template <typename T>
ObjWrap(T O) : Self(new Obj<T>(std::move(O))) {}
template <typename T>
T * getObjPtr() {
return static_cast<T*>(Self->getObjPtr_());
}
private:
struct Concept {
virtual ~Concept() = 0;
virtual void* getObjPtr_() = 0;
};
template <typename T>
struct Obj : Concept {
Obj(T O) : Data(std::move(O)) {}
void* getObjPtr_() { return static_cast<void*>(&Data); }
T Data;
};
std::unique_ptr<Concept> Self;
};
int main() {
ObjWrap a(1);
ObjWrap b(std::string("b"));
int* p_a = a.getObjPtr<int>();
std::string* p_b = b.getObjPtr<std::string>();
std::cout << *p_a << " " << *p_b << "n";
return 0;
}
此版本的代码与上述代码之间的主要区别在于,T * getObjPtr()
是由ObjWrap
对象封装的成员函数。
编辑3:
我关于类型擦除的问题得到了接受的答案的回答。但是,关于同时类型实例化到多个模板的问题尚未得到解答。我的猜测是目前C++不允许这样做,但很高兴听到有更多经验的人的意见。
有几件事可能会有所帮助。
首先要说的是,如果 Obj 需要公开对象的地址,它不是 Sean Parent 的"继承是万恶之源"的类型擦除容器。
诀窍是确保 Obj 的接口提供包装器需要的所有语义操作和查询。
为了提供这一点,在概念中缓存对象的地址及其type_id
通常是一个合理的想法。
请考虑以下更新的示例,其中有一个公共方法 - operator==。规则是,如果两个 Obj 包含相同类型的对象并且这些对象比较相等,则它们相等。
请注意,地址和type_id:
1( 是实现细节,未在 Obj 的界面上公开
2( 无需虚拟调用即可访问,这会短路不相等的情况。
#include <memory>
#include <utility>
#include <typeinfo>
#include <utility>
#include <cassert>
#include <iostream>
class ObjWrap
{
public:
template <typename T>
ObjWrap(T O) : Self(new Model<T>(std::move(O))) {}
// objects are equal if they contain the same type of model
// and the models compare equal
bool operator==(ObjWrap const& other) const
{
// note the short-circuit when the types are not the same
// this means is_equal can guarantee that the address can be cast
// without a further check
return Self->info == other.Self->info
&& Self->is_equal(other.Self->addr);
}
bool operator!=(ObjWrap const& other) const
{
return !(*this == other);
}
friend std::ostream& operator<<(std::ostream& os, ObjWrap const& o)
{
return o.Self->emit(os);
}
private:
struct Concept
{
// cache the address and type here in the concept.
void* addr;
std::type_info const& info;
Concept(void* address, std::type_info const& info)
: addr(address)
, info(info)
{}
virtual ~Concept() = default;
// this is the concept's interface
virtual bool is_equal(void const* other_address) const = 0;
virtual std::ostream& emit(std::ostream& os) const = 0;
};
template <typename T>
struct Model : Concept
{
Model(T O)
: Concept(std::addressof(Data), typeid(T))
, Data(std::move(O)) {}
// no need to check the pointer before casting it.
// Obj takes care of that
/// @pre other_address is a valid pointer to a T
bool is_equal(void const* other_address) const override
{
return Data == *(static_cast<T const*>(other_address));
}
std::ostream& emit(std::ostream& os) const override
{
return os << Data;
}
T Data;
};
std::unique_ptr<Concept> Self;
};
int main()
{
auto x = ObjWrap(std::string("foo"));
auto y = ObjWrap(std::string("foo"));
auto z = ObjWrap(int(2));
assert(x == y);
assert(y != z);
std::cout << x << " " << y << " " << z << std::endl;
}
http://coliru.stacked-crooked.com/a/dcece2a824a42948
- 等等(如果这是错误的,请纠正我...
你的前提至少在原则上是错误的,如果不是在实践中也是错误的。您坚持要使getObjPtr()
成为虚拟方法,并使用抽象基类。但是 - 你还没有确定这是必要的。请记住 - 使用虚拟方法很昂贵!为什么我应该为虚拟付费才能获得类型擦除?
有没有办法修复上面的代码,以便我们可以简单地使用
int* p_a = getObjPtr(a)
把肖恩·帕伦特的演讲标题放在心上(而不是他在演讲中使用继承的事实(,放弃继承,答案应该是肯定的。编辑:擦除类型的代码和取消擦除类型的代码知道类型是什么就足够了 - 只要您不需要以特定于类型的方式对类型擦除的数据进行操作。在肖恩·帕伦特的演讲中,你需要能够制作它的重要副本,移动它,绘制它等。对于std::any
/boost::any
您可能需要复制和移动,这可能需要虚拟 - 但这是最普遍的用例。
即使std::any
也限制了你能做什么和不能做什么,正如这个问题中所讨论的:
为什么 std::any_cast 不支持隐式转换?
- 将数组的地址分配给变量并删除
- vector.resize()中的分配错误
- 如果C++类在类方法中具有动态分配,但没有构造函数/析构函数或任何非静态成员,那么它仍然是POD类型吗
- Win32编译器选项和内存分配
- 函数中堆分配的效果与缺少堆分配的情况
- 使用动态分配的数组会导致代码分析发出虚假的C6386缓冲区溢出警告
- 多个文件的内存分配错误"在抛出 'std :: bad_alloc' what (): std :: bad_alloc 的实例后终止调用" [C++]
- 获取字符串的长度并将其分配给数组
- 将地址分配给本地指针后,公共对象的变量将消失
- 递归模板化函数不能分配给具有常量限定类型"const tt &"的变量"state"
- 有没有一种方法可以使用placement new将堆叠对象分配给分配的内存
- 我在二维向量中是否正确分配了内存
- 正在尝试重载二进制搜索树分配运算符
- GlobalAlloc而不是其他分配方法
- 自定义先决条件对移动分配运算符有效吗
- 我可以重新分配/覆盖std::字符串吗
- 在c++中使用动态分配的问题
- 当一个新对象被分配到它的地址时,对象是否必须被销毁
- 为什么我可以使用比分配的内存更多的内存
- 使用RAII在给定次数的迭代后重新分配资源