我有以下功能模板:
template <class MostDerived, class HeldAs>
HeldAs* duplicate(MostDerived *original, HeldAs *held)
{
// error checking omitted for brevity
MostDerived *copy = new MostDerived(*original);
std::uintptr_t distance = reinterpret_cast<std::uintptr_t>(held) - reinterpret_cast<std::uintptr_t>(original);
HeldAs *copyHeld = reinterpret_cast<HeldAs*>(reinterpret_cast<std::uintptr_t>(copy) + distance);
return copyHeld;
}
目的是复制特定类型的对象,并通过与输入相同的子对象将其“保持”。请注意,原则上HeldAs
可能是MostDerived
的模糊或不可访问的基类,因此没有演员可以在这里提供帮助。
这是我的代码,但它可以用于我无法控制的类型(即我无法修改MostDerived
或HeldAs
)。该函数具有以下前提条件:
*original
属于动态类型MostDerived
HeldAs
是MostDerived
或MostDerived
的直接或间接基类(忽略cv-qualifiation)*held
引用*original
或其基类子对象之一。让我们假设前提条件令人满意。 duplicate
在这种情况下是否定义了行为?
C ++ 11 [expr.reinterpret.cast]说(大胆强调我的):
4指针可以显式转换为足以容纳它的任何整数类型。映射功能是 实现定义。 [注意:对于那些了解寻址结构的人来说,这并不奇怪 底层机器。 -end note ] ...
5可以将整数类型或枚举类型的值显式转换为指针。转换的指针 到一个足够大小的整数(如果在实现上存在任何这样的整数)并返回到相同的指针类型 将具有其原始价值; 指针和整数之间的映射是实现定义的。 [注意:除3.7.4.3中所述外,此类转换的结果不是安全派生的指针 值。 -end note ]
好吧,假设我的编译器是GCC(或Clang,因为它使用GCC的实现定义行为的定义)。引用GCC docs chapter 5关于C ++实现定义的行为:
... C语言的相应文档中记录了一些选项。见C Implementation。 ...
On chapter 4.7(C实现,数组和指针):
将指针转换为整数的结果,反之亦然(C90 6.3.4,C99和C11 6.3.2.3)。
如果指针表示大于整数类型,则从指针到整数的转换会丢弃最高有效位,如果指针表示小于整数类型,则签名扩展,否则位不变。
如果指针表示小于整数类型,则从整数到指针的强制转换丢弃最高有效位,如果指针表示大于整数类型,则根据整数类型的符号扩展,否则位不变
到目前为止,这么好。看来,因为我使用的std::uintptr_t
保证对任何指针足够大,并且因为我处理相同的类型,copyHeld
应该指向相同的HeldAs
*copy
的{{1}}子对象指向held
内。
不幸的是,GCC文档中还有一个段落:
当从指针转换为整数并再次返回时,结果指针必须引用与原始指针相同的对象,否则行为未定义。也就是说,可能不会使用整数运算来避免指针运算的未定义行为,如C99和C11 6.5.6 / 8中所禁止的那样。
重击。所以现在似乎即使*original
的值是根据前两段的规则计算的,第三个仍然会将其发送到Undefined-Behavior域。
我基本上有三个问题:
我的阅读是否正确且copyHeld
的行为未定义?
这是哪种未定义的行为? “正式未定义,但无论如何都会做你想要的”,或“期望随机崩溃和/或自发的自焚”一个?
如果它确实是未定义的,有没有办法以明确定义的(可能依赖于编译器的)方式做这样的事情?
虽然就编译器而言,我的问题仅限于GCC(和Clang)行为,但我欢迎一个考虑所有类型硬件平台的答案,从普通桌面到异国情调。
答案 0 :(得分:0)
通常的模式是在基类中放置clone()
然后每个派生类都可以实现自己的克隆版本。
class Base
{
public:
virtual Base* clone() = 0;
};
class D: public Base
{
virtual Base* clone(){ return new D(*this);}
};