有没有办法通过引用模拟向下转换
Is there a way to simulate downcasting by reference
所以,我有一些与这些结构类似的东西:
struct Generic {}
struct Specific : Generic {}
在某些时候我有消沉的需要,即:
Specific s = (Specific) GetGenericData();
这是个问题,因为我收到错误消息,指出没有可用的用户定义转换。
我可以将代码更改为:
Specific s = (*(Specific *)&GetGenericData())
或使用reinterpret_cast,它将是:
Specific s = *reinterpret_cast<Specific *>(&GetGenericData());
但是,有没有办法让这个更干净?也许使用宏或模板?
我查看了这个 post C++ covariant templates,我认为它有一些相似之处,但不确定如何针对我的情况重写它。我真的不想将事物定义为 SmartPtr。我宁愿让事物保持原样。
从您的用法看来 GetGenericData()
return 是一个 Generic
按值,在这种情况下,转换为 Specific
将是不安全的,因为 object slicing.
做你想做的事,你应该return一个指针或引用:
Generic* GetGenericData();
Generic& GetGenericDataRef();
然后你就可以进行演员表了:
// safe, returns nullptr if it's not actually a Specific*
auto safe = dynamic_cast<Specific*>(GetGenericData());
// for references, this will throw std::bad_cast
// if you try the wrong type
auto& safe_ref = dynamic_cast<Specific&>(GetGenericDataRef());
// unsafe, undefined behavior if it's the wrong type,
// but faster if it is
auto unsafe = static_cast<Specific*>(GetGenericData());
所以第一步是要意识到你有动态问题。您存储的状态的性质会根据动态信息而变化。
struct GenericState { virtual ~GenericState() {} }; // data in here
struct Generic;
template<class D>
struct GenericBase {
D& self() { return *static_cast<D&>(*this); }
D const& self() const { return *static_cast<D&>(*this); }
// code to interact with GenericState here via self().pImpl
// if you have `virtual` behavior, have a non-virtual method forward to
// a `virtual` method in GenericState.
};
struct Generic:GenericBase<Generic> {
// ctors go here, creates a GenericState in the pImpl below, or whatever
~GenericState() {} // not virtual
private:
friend struct GenericBase<Generic>;
std::unique_ptr<GenericState> pImpl;
};
struct SpecificState : GenericState {
// specific stuff in here, including possible virtual method overrides
};
struct Specific : GenericBase<Specific> {
// different ctors, creates a SpecificState in a pImpl
// upcast operators:
operator Generic() && { /* move pImpl into return value */ }
operator Generic() const& { /* copy pImpl into return value */ }
private:
friend struct GenericBase<Specific>;
std::unique_ptr<SpecificState> pImpl;
};
如果您想要复制的能力,请在 GenericState
中实现一个 virtual GenericState* clone() const
方法,并在 SpecificState
中协变地覆盖它。
我在这里所做的是对类型进行正则化(如果我们不支持移动,则进行半正则化)。 Specific 和 Generic 类型不相关,但它们的后端实现细节(GenericState 和 SpecificState)是相关的。
主要通过 CRTP 和 GenericBase
.
避免了接口重复
向下转换现在可以涉及或不涉及动态检查。您通过 pImpl
并将其投过来。如果在右值上下文中完成,它会移动——如果在左值上下文中,它会复制。
如果您愿意,可以使用共享指针而不是唯一指针。这将允许基于非复制非移动的铸造。
好的,经过一些额外的研究,我想知道这样做有什么问题:
struct Generic {}
struct Specific : Generic {
Specific( const Generic &obj ) : Generic(obj) {}
}
如果我错了请纠正我,但这可以使用隐式复制构造函数。
假设是这种情况,我可以避免编写一个并自动执行转换,现在我可以写:
Specific s = GetGenericData();
当然,对于大对象,这可能不是一个好主意,但对于较小的对象,这会是 "correct" 解决方案吗?
我假设你的数据很简单。
struct Generic {
int x=0;
int y=0;
};
struct Specific:Generic{
int z=0;
explicit Specific(Generic const&o):Generic(o){}
// boilerplate, some may not be needed, but good habit:
Specific()=default;
Specific(Specific const&)=default;
Specific(Specific &&)=default;
Specific& operator=(Specific const&)=default;
Specific& operator=(Specific &&)=default;
};
鲍勃是你的叔叔。 int z
有一个默认的初始值设定项有点重要,因此我们不必在父构造器中重复它。
我使 thr ctor 显式化,因此它只会显式调用,而不是意外调用。
这是适合简单数据的解决方案。
所以,我有一些与这些结构类似的东西:
struct Generic {}
struct Specific : Generic {}
在某些时候我有消沉的需要,即:
Specific s = (Specific) GetGenericData();
这是个问题,因为我收到错误消息,指出没有可用的用户定义转换。
我可以将代码更改为:
Specific s = (*(Specific *)&GetGenericData())
或使用reinterpret_cast,它将是:
Specific s = *reinterpret_cast<Specific *>(&GetGenericData());
但是,有没有办法让这个更干净?也许使用宏或模板?
我查看了这个 post C++ covariant templates,我认为它有一些相似之处,但不确定如何针对我的情况重写它。我真的不想将事物定义为 SmartPtr。我宁愿让事物保持原样。
从您的用法看来 GetGenericData()
return 是一个 Generic
按值,在这种情况下,转换为 Specific
将是不安全的,因为 object slicing.
做你想做的事,你应该return一个指针或引用:
Generic* GetGenericData();
Generic& GetGenericDataRef();
然后你就可以进行演员表了:
// safe, returns nullptr if it's not actually a Specific*
auto safe = dynamic_cast<Specific*>(GetGenericData());
// for references, this will throw std::bad_cast
// if you try the wrong type
auto& safe_ref = dynamic_cast<Specific&>(GetGenericDataRef());
// unsafe, undefined behavior if it's the wrong type,
// but faster if it is
auto unsafe = static_cast<Specific*>(GetGenericData());
所以第一步是要意识到你有动态问题。您存储的状态的性质会根据动态信息而变化。
struct GenericState { virtual ~GenericState() {} }; // data in here
struct Generic;
template<class D>
struct GenericBase {
D& self() { return *static_cast<D&>(*this); }
D const& self() const { return *static_cast<D&>(*this); }
// code to interact with GenericState here via self().pImpl
// if you have `virtual` behavior, have a non-virtual method forward to
// a `virtual` method in GenericState.
};
struct Generic:GenericBase<Generic> {
// ctors go here, creates a GenericState in the pImpl below, or whatever
~GenericState() {} // not virtual
private:
friend struct GenericBase<Generic>;
std::unique_ptr<GenericState> pImpl;
};
struct SpecificState : GenericState {
// specific stuff in here, including possible virtual method overrides
};
struct Specific : GenericBase<Specific> {
// different ctors, creates a SpecificState in a pImpl
// upcast operators:
operator Generic() && { /* move pImpl into return value */ }
operator Generic() const& { /* copy pImpl into return value */ }
private:
friend struct GenericBase<Specific>;
std::unique_ptr<SpecificState> pImpl;
};
如果您想要复制的能力,请在 GenericState
中实现一个 virtual GenericState* clone() const
方法,并在 SpecificState
中协变地覆盖它。
我在这里所做的是对类型进行正则化(如果我们不支持移动,则进行半正则化)。 Specific 和 Generic 类型不相关,但它们的后端实现细节(GenericState 和 SpecificState)是相关的。
主要通过 CRTP 和 GenericBase
.
向下转换现在可以涉及或不涉及动态检查。您通过 pImpl
并将其投过来。如果在右值上下文中完成,它会移动——如果在左值上下文中,它会复制。
如果您愿意,可以使用共享指针而不是唯一指针。这将允许基于非复制非移动的铸造。
好的,经过一些额外的研究,我想知道这样做有什么问题:
struct Generic {}
struct Specific : Generic {
Specific( const Generic &obj ) : Generic(obj) {}
}
如果我错了请纠正我,但这可以使用隐式复制构造函数。
假设是这种情况,我可以避免编写一个并自动执行转换,现在我可以写:
Specific s = GetGenericData();
当然,对于大对象,这可能不是一个好主意,但对于较小的对象,这会是 "correct" 解决方案吗?
我假设你的数据很简单。
struct Generic {
int x=0;
int y=0;
};
struct Specific:Generic{
int z=0;
explicit Specific(Generic const&o):Generic(o){}
// boilerplate, some may not be needed, but good habit:
Specific()=default;
Specific(Specific const&)=default;
Specific(Specific &&)=default;
Specific& operator=(Specific const&)=default;
Specific& operator=(Specific &&)=default;
};
鲍勃是你的叔叔。 int z
有一个默认的初始值设定项有点重要,因此我们不必在父构造器中重复它。
我使 thr ctor 显式化,因此它只会显式调用,而不是意外调用。
这是适合简单数据的解决方案。