请不必分配任何新对象通过指针互换类类型仅铸造?(Make interchangeable class

2019-06-25 19:06发布

UPDATE: I do appreciate "don't want that, want this instead" suggestions. They are useful, especially when provided in context of the motivating scenario. Still...regardless of goodness/badness, I've become curious to find a hard-and-fast "yes that can be done legally in C++11" vs "no it is not possible to do something like that".


I want to "alias" an object pointer as another type, for the sole purpose of adding some helper methods. The alias cannot add data members to the underlying class (in fact, the more I can prevent that from happening the better!) All aliases are equally applicable to any object of this type...it's just helpful if the type system can hint which alias is likely the most appropriate.

There should be no information about any specific alias that is ever encoded in the underlying object. Hence, I feel like you should be able to "cheat" the type system and just let it be an annotation...checked at compile time, but ultimately irrelevant to the runtime casting. Something along these lines:

Node<AccessorFoo>* fooPtr = Node<AccessorFoo>::createViaFactory();
Node<AccessorBar>* barPtr = reinterpret_cast< Node<AccessorBar>* >(fooPtr);

Under the hood, the factory method is actually making a NodeBase class, and then using a similar reinterpret_cast to return it as a Node<AccessorFoo>*.

The easy way to avoid this is to make these lightweight classes that wrap nodes and are passed around by value. Thus you don't need casting, just Accessor classes that take the node handle to wrap in their constructor:

AccessorFoo foo (NodeBase::createViaFactory());
AccessorBar bar (foo.getNode());

But if I don't have to pay for all that, I don't want to. That would involve--for instance--making a special accessor type for each sort of wrapped pointer (AccessorFooShared, AccessorFooUnique, AccessorFooWeak, etc.) Having these typed pointers being aliased for one single pointer-based object identity is preferable, and provides a nice orthogonality.

So back to that original question:

Node<AccessorFoo>* fooPtr = Node<AccessorFoo>::createViaFactory();
Node<AccessorBar>* barPtr = reinterpret_cast< Node<AccessorBar>* >(fooPtr);

Seems like there would be some way to do this that might be ugly but not "break the rules". According to ISO14882:2011(e) 5.2.10-7:

An object pointer can be explicitly converted to an object pointer of a different type.70 When a prvalue v of type "pointer to T1" is converted to the type "pointer to cv T2", the result is static_cast(static_cast(v)) if both T1 and T2 are standard-layout types (3.9) and the alignment requirements of T2 are no stricter than those of T1, or if either type is void. Converting a prvalue of type "pointer to T1" to the type "pointer to T2" (where T1 and T2 are object types and where the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer value. The result of any other such pointer conversion is unspecified.

Drilling into the definition of a "standard-layout class", we find:

  • has no non-static data members of type non-standard-layout-class (or array of such types) or reference, and
  • has no virtual functions (10.3) and no virtual base classes (10.1), and
  • has the same access control (clause 11) for all non-static data members, and
  • has no non-standard-layout base classes, and
  • either has no non-static data member in the most-derived class and at most one base class with non-static data members, or has no base classes with non-static data members, and
  • has no base classes of the same type as the first non-static data member.

Sounds like working with something like this would tie my hands a bit with no virtual methods in the accessors or the node. Yet C++11 apparently has std::is_standard_layout to keep things checked.

Can this be done safely? Appears to work in gcc-4.7, but I'd like to be sure I'm not invoking undefined behavior.

Answer 1:

术语访问者是一个大破绽:你正在寻找的是一个代理

没有理由为代理不按值传递左右。

// Let us imagine that NodeBase is now called Node, since there is no inheritance

class AccessorFoo {
public:
    AccessorFoo(Node& n): node(n) {}

    int bar() const { return node->bar; }

private:
    std::reference_wrapper<Node> node;
};

然后你就可以随意转换,从一个访问到另一个......虽然这气味 。 通常有一个访问的非常目标是限制的方式访问,因此铸造愿意不愿意威利另一个访问是坏的 。 但是一个可以支持铸造较窄的访问。



Answer 2:

如果我理解正确的话,你必须:

  • 一个NodeBase类是有状态的,系统的真正主力;
  • 一组无状态的Accessor类型提供到接口NodeBase ; 和
  • 一个Node<AccessorT>类包装一个访问器,想必提供方便的功能。

我假设的最后一位,因为如果你没有,做便利的东西的包装类型,那么没有理由不使Accessor类型的顶层,像你这样的建议:通过AccessorFooAccessorBar周围的值。 他们是不是同一个对象的事实是完全没有实际意义; 如果你认为他们像他们的三分球,那么你会注意到, &foo != &bar没有比其更有趣NodeBase* p1 = new NodeBase; NodeBase* p2 = p1; NodeBase* p1 = new NodeBase; NodeBase* p2 = p1; 并指出,当然, &p1 != &p2

如果你真的需要一个包装Node<AccessorT>并希望把它的标准布局,那么我建议您使用的无国籍Accessor的类型,你的优势。 如果他们仅仅是功能性的无状态的容器(他们必须,否则为什么你能够自由地施展他们吗?),那么你可以做这样的事情:

struct AccessorFoo {
    int getValue(NodeBase* n) { return n->getValueFoo(); }
};

struct AccessorBar {
    int getValue(NodeBase* n) { return n->getValueBar(); }
};

template <typename AccessorT>
class Node {
    NodeBase* m_node;

public:
    int getValue() {
        AccessorT accessor;
        return accessor.getValue(m_node);
    }
};

在这种情况下,你可以添加一个模板转换操作符:

template <typename OtherT>
operator Node<OtherT>() {
    return Node<OtherT>(m_node);
}

任何从现在你已经有了直接的价值转换Node<AccessorT>键入你喜欢。

如果你把它只是一个远一点,你会做的所有方法Accessor类型的静态,并在到达特征的图案 。


您引用C ++标准的部分,顺便说一句,涉及的行为reinterpret_cast<T*>(p)中,这两个源类型和最终类型是指向标准布局对象的情况下,在这种情况下,标准的保证你得到你从铸造到一个得到同样的指针void* ,然后到最后的类型。 你仍然没有得到使用对象比它作为而不调用不确定的行为创建的类型以外的任何类型。



Answer 3:

我相信,严格别名规则禁止你正在尝试做的。

为了澄清:严格别名无关与布局的兼容性,POD类型或什么不是。 它与优化做的。 以怎样的语言明确禁止你做的。

本文概括起来相当不错: http://dbp-consulting.com/StrictAliasing.pdf



Answer 4:

struct myPOD {
   int data1;
   // ...
};

struct myPOD_extended1 : myPOD {
   int helper() { (*(myPOD*)this)->data1 = 6; };  // type myPOD referenced here
};
struct myPOD_extended2 : myPOD { 
   int helper() { data1 = 7; };                   // no syntactic ref to myPOD
};
struct myPOD_extended3 : myPOD {
   int helper() { (*(myPOD*)this)->data1 = 8; };  // type myPOD referenced here
};
void doit(myPOD *it)
{
    ((myPOD_extended1*)it)->helper();
    // ((myPOD_extended2*)it)->helper(); // uncomment -> program/compile undefined
    ((myPOD_extended3*)it)->helper();
}

int main(int,char**)
{
    myPOD a; a.data1=5; 
    doit(&a);

    std::cout<< a.data1 << '\n';
    return 0;
}

我相信这是保证在所有符合C ++编译器工作,必须打印8.取消注释标记行和所有的赌注都关闭。

优化器可以通过检查语法类型对列表中的函数实际上引用的句法类型它需要产生正确的结果为参考的列表(3.10p10)修剪为有效别名引用搜索 - 当实际(“动态“)的对象类型是已知的,该列表不包括通过以任何派生类型的引用的访问。 所以明确的(合法的)向下转换thismyPOD*helper() s让myPOD的语法引用有类型列表中,并且优化了治疗所产生的引用作为潜在的法律引用(其他的名字,别名的)对象a



文章来源: Make interchangeable class types via pointer casting only, without having to allocate any new objects?