Make interchangeable class types via pointer casting only, without having to allocate any new objects?

元气小坏坏 提交于 2019-11-29 05:10:00

The term Accessor is a dead giveaway: what you are looking for is a Proxy.

There is no reason for a Proxy not to be passed around by value.

// 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;
};

And then you can freely convert from one accessor to another... though this smells. Normally the very goal of having an accessor is to restrict access in a way, so casting nilly willy to another accessor is bad. However one could support casting to a narrower accessor.

If I understand you correctly, you have:

  • A NodeBase class that is stateful, and the true workhorse of the system;
  • a set of stateless Accessor types that provide an interface to NodeBase; and
  • a Node<AccessorT> class which wraps an accessor, presumably providing convenience functions.

I assume the last bit because if you don't have a wrapper type that does convenience stuff, then there's no reason not to make the Accessor types your top-level, like you suggested: pass AccessorFoo and AccessorBar around by value. The fact that they aren't the same object is entirely moot; if you think of them like the pointers that they are, then you'll note that &foo != &bar is no more interesting than having NodeBase* p1 = new NodeBase; NodeBase* p2 = p1; and noting that, of course, &p1 != &p2.

If you really do need a wrapper Node<AccessorT> and want to make it standard-layout, then I would suggest that you use the statelessness of your Accessor types to your advantage. If they are simply a stateless container of functionality (which they must be; why else would you be able to freely cast them?), then you can do something like this:

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);
    }
};

In this case, you could add a templated conversion operator:

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

And now you've got direct value conversion between any Node<AccessorT> type you like.

If you take it just a bit further, you'll make all the methods of the Accessor types static, and arrive at the traits pattern.


The section of the C++ standard that you quoted, incidentally, concerns the behavior of reinterpret_cast<T*>(p) in the case that both the source type and the final type are pointers to standard-layout objects, in which case the standard guarantees that you get the same pointer you'd get from casting to a void* and then to the final type. You still don't get to use the object as any type other than the type it was created as without invoking undefined behavior.

I believe the strict aliasing rules forbid what you are trying to do.

To clarify: strict aliasing has nothing to do with layout compatibility, POD types or what not. It has to do with optimization. And with what the language explicitly forbids you to do.

This paper sums it up rather well: http://dbp-consulting.com/StrictAliasing.pdf

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;
}

I believe this is guaranteed to work in all conforming C++ compilers and must print 8. Uncomment the marked line and all bets are off.

An optimizer might prune searches for valid aliased references by checking the list of syntactic types actually referenced in a function against the list (in 3.10p10) of syntactic types of references it's required to produce correct results for -- and when an actual ("dynamic") object type is known, that list doesn't include access through a reference to any derived type. So the explicit (and valid) downcasts of this to myPOD* in the helper()s puts myPOD on the list of types syntactically referenced there, and the optimizer has to treat the resulting references as potential legal references to (other names for, aliases of) the object a.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!