此问题基于以下示例代码,该示例代码受Sean Parent's talk的启发。 以下代码的目标是提供类似于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
对象的编译时实例化T* getObjPtr(ObjWrap)
构造函数和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 ++不允许这样做,但是很高兴听到有更多经验的人的话。
答案 0 :(得分:2)
有些事情可能会有所帮助。
首先要说的是,如果Obj曾经需要公开对象的地址,那不是Sean Parent的“继承是万恶之源”类型擦除容器。
诀窍是确保Obj的接口提供所有语义动作和包装程序将需要的查询。
为了提供这一点,通常将一个对象的地址及其type_id
缓存在该概念中是一个合理的想法。
请考虑以下更新的示例,其中有一个公共方法-operator ==。规则是,如果两个Objs包含相同类型的对象并且这些对象比较相等,则它们相等。
请注意,地址和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;
}
答案 1 :(得分:0)
- (等)。如果这是错误的,请纠正我...
您的前提至少在原则上是错误的,如果在实践中也没有。您坚持要使getObjPtr()
为虚拟方法,并使用抽象基类。但是-您尚未确定这是必要的。记住-使用虚拟方法很昂贵!为什么我要为获得类型擦除而为虚拟机付费?
有没有一种方法可以修复上面的代码,以便我们可以简单地使用
int* p_a = getObjPtr(a)
牢记Sean Parent的谈话标题(而不是在谈话中确实使用继承的事实),放弃继承,答案应为“是”。 编辑:只要您不需要 act 以特定于类型的方式对类型擦除的数据进行处理。在Sean Parent的谈话中,您需要能够对其进行非平凡的复制,移动,绘制等。使用std::any
/ boost::any
,您可能需要复制和移动,这可能需要虚拟机-但这是最普通的用例。
std::any
甚至限制了您可以做和不能做的事情,如本问题所述: