派生自其实例存在于固定格式(数据库,MMF)的基类...如何安全? [英] Deriving from a base class whose instances reside in a fixed format (database, MMF)...how to be safe?

查看:202
本文介绍了派生自其实例存在于固定格式(数据库,MMF)的基类...如何安全?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

注意:我正在查找有关正确搜索字词的任何建议,以了解此类问题。对象关系映射发生在我作为一个地方,我可以找到一些好的现有技术...但我还没有看到什么非常适合这种情况。)

(Note: I'm looking for really any suggestions on the right search terms to read up on this category of issue. "Object-relational-mapping" occurred to me as a place where I could find some good prior art...but I haven't seen anything quite fitting this scenario just yet.)

我有一个非常通用的类Node 你可以认为它有点像DOM树中的元素。这不是正在发生的 - 他们在内存映射文件中的数据库对象。但是类比对于所有实际目的来说都是相当接近的,所以为了简单起见,我将坚持使用DOM术语。

I have a very generic class Node, which for the moment you can think of as being a bit like an element in a DOM tree. This is not precisely what's going on--they're graph database objects in a memory mapped file. But the analogy is fairly close for all practical purposes, so I'll stick to DOM terms for simplicity.

嵌入在节点中的标签你应该(理想地)能够做到的操作。现在我使用派生类来做到这一点。例如,如果您试图表示类似HTML列表的内容:

The "tag" embedded in the node implies a certain set of operations you should (ideally) be able to do with it. Right now I'm using derived classes to do this. So for instance, if you were trying to represent something like an HTML list:

<ul>
   <li>Coffee</li>
   <li>Tea</li>
   <li>Milk</li>
</ul>

底层树将是七个节点:

+--UL                       // Node #1
   +--LI                    // Node #2
      +--String(Coffee)     // Node #3 (literal text)
   +--LI                    // Node #4
      +--String(Tea)        // Node #5 (literal text)
   +--LI                    // Node #6
      +--String(Milk)       // Node #7 (literal text)

code> getString()已经是节点本身的一个原始方法,我可能只做 class UnorderedListNode:public Node code> class ListItemNode:public Node 。

Since getString() is already a primitive method on Nodes themselves, I'd probably only make class UnorderedListNode : public Node, class ListItemNode : public Node.

继续这个假设,让我们想象一下我想帮助程序员使用较少的一般函数他们知道更多关于他们手中的类型/标签。也许我想帮助他们树上的结构性习语,例如添加一个字符串项到无序列表,或提取的东西作为字符串。

Continuing this hypothetical, let's imagine I wanted to help the programmer use less general functions when they know more about the Node "type"/tag they have in their hands. Perhaps I want to assist them with structural idioms on the tree, like adding a string item to an unordered list, or extracting things as a string. (This is just an analogy so don't take the routines too seriously.)

class UnorderedListNode : public Node {
private:
    // Any data members someone put here would be a mistake!

public:
    static boost::optional<UnorderedListNode&> maybeCastFromNode(Node& node) {
        if (node.tagString() == "ul") {
            return reinterpret_cast<UnorderedListNode&>(node);
        }
        return boost::none;
    }

    // a const helper method
    vector<string> getListAsStrings() const {
        vector<string> result;
        for (Node const* childNode : children()) {
            result.push_back(childNode->children()[0]->getText());
        }
        return result;
    }

    // helper method requiring mutable object
    void addStringToList(std::string listItemString) {
        unique_ptr<Node> liNode (new Node (Tag ("LI"));
        unique_ptr<Node> textNode (new Node (listItemString));
        liNode->addChild(std::move(textNode));
        addChild(std::move(liNode));
    }
};


$ b b

将数据成员添加到这些新的派生类是一个坏主意。唯一的方法是真正持久化任何信息是使用Node的基本例程(例如 addChild 调用上面或 getText )与树进行交互,因此真正的继承模型什么使得< UL> 节点maybeCast成为一个 UnorderedListNode 与vtables / etc。

Adding data members to these new derived classes is a bad idea. The only way to really persist any information is to use the foundational routines of Node (for instance, the addChild call above, or getText) to interact with the tree. Thus the real inheritance model--to the extent one exists--is outside of the C++ type system. What makes a <UL> node "maybeCast" into an UnorderedListNode has nothing to do with vtables/etc.

C ++继承看起来有时,但感觉通常是错误的我觉得而不是继承我应该有类独立存在的Node,只是协作

C++ inheritance looks right sometimes, but feels wrong usually. I feel like instead of inheritance I should have classes that exist independently of Node, and just collaborate with it somehow as "accessor helpers"...but I don't have a good grasp of what that would be like.

推荐答案

C ++继承有时看起来很正常,但通常感觉不对。

"C++ inheritance looks right sometimes, but feels wrong usually."

确实,这个语句是令人担忧的:

Indeed, and this statement is worrisome:


使节点maybeCast成为UnorderedListNode与vtables / etc无关

What makes a node "maybeCast" into an UnorderedListNode has nothing to do with vtables/etc.

正如以下代码:

static boost::optional<UnorderedListNode&> maybeCastFromNode(Node& node) {
    if (tagString() == "ul") {
        return reinterpret_cast<UnorderedListNode&>(node);
    }
    return boost::none;
}

(1) type-punning

如果传递的 Node& 是通过一个没有合法和正确构造 UnorderedListNode 在继承路径上,这就是所谓的类型打孔。这几乎总是一个坏主意。即使在没有虚拟函数的情况下大多数编译器上的内存布局出现时,派生类不会添加数据成员,但在大多数情况下都可以自由破解。

If the Node& being passed in was allocated through a mechanism that did not legally and properly construct an UnorderedListNode on the inheritance path, this is what is called type punning. It's almost always a bad idea. Even if the memory layout on most compilers appears to work when there are no virtual functions and derived classes add no data members, they are free to break it in most all circumstances.

(2)

接下来是编译器的假设,指向基本不同类型的对象的指针不会 。这是严格混叠要求。虽然它可以通过非标准扩展禁用,但只应用于旧版情况...它会阻碍优化。

Next there is the compiler's assumption that pointers to objects of fundamentally different types do not "alias" each other. This is the strict aliasing requirement. Although it can be disabled via non-standard extensions, that should only be applied in legacy situations...it hinders optimization.

这还不完全清楚 - 从学术立场 - 这两个障碍是否在特殊情况下具有规范允许的解决方法。这是一个调查这个问题的问题,在写作时仍然是一个公开讨论:

It's not completely clear--from an academic standpoint--whether these two hindrances have workarounds permitted by the spec under special circumstances. Here's a question which investigates that, and is still an open discussion at time of writing:

只通过指针转换来创建可互换类类型,而不必分配任何新对象?

引用@MatthieuM。越接近规范的边缘,越有可能遇到编译器错误。因此,作为工程师,我建议务实,避免使用编译器玩头脑游戏;无论你是对还是错都是无关紧要的:当你在生产代码中遇到崩溃时,你会失败,而不是编译器的写者。

But to quote @MatthieuM.: "The closer you get to the edges of the specifications, the more likely you are to hit a compiler bug. So, as engineer, I advise to be pragmatic and avoid playing mind games with your compiler; whether you are right or wrong is irrelevant: when you get a crash in production code, you lose, not the compiler writers."

这可能是更正确的轨道:

This is probably more the right track:


我觉得而不是继承我应该有独立存在的类的节点,并只是与它作为访问器助手合作,但我没有很好的把握是什么。

I feel like instead of inheritance I should have classes that exist independently of Node, and just collaborate with it somehow as "accessor helpers"...but I don't have a good grasp of what that would be like.

使用设计模式字词,它与代理类似。你将有一个轻量级对象存储指针,然后通过值传递。在实践中,处理诸如关于正在包装的传入指针的 const 的问题可能是棘手的!

Using Design Pattern terms, this matches something like a Proxy. You would have a lightweight object that stores the pointer and is then passed around by value. In practice, it can be tricky to handle issues like what to do about the const-ness of the incoming pointers being wrapped!

这里是一个示例,说明如何在这种情况下相对简单。首先,对Accessor基类的定义:

Here's a sample of how it might be done relatively simply for this case. First, a definition for the Accessor base class:

template<class AccessorType> class Wrapper;

class Accessor {
private:
    mutable Node * nodePtrDoNotUseDirectly;
template<class AccessorType> friend class Wrapper;
    void setNodePtr(Node * newNodePtr) {
        nodePtrDoNotUseDirectly = newNodePtr;
    }
    void setNodePtr(Node const * newNodePtr) const {
        nodePtrDoNotUseDirectly = const_cast<Node *>(newNodePtr);
    }
    Node & getNode() { return *nodePtrDoNotUseDirectly; }
    Node const & getNode() const { return *nodePtrDoNotUseDirectly; }

protected:
    Accessor() {}

public:
    // These functions should match Node's public interface
    // Library maintainer must maintain these, but oh well
    inline void addChild(unique_ptr<Node>&& child)) { 
        getNode().addChild(std::move(child));
    }
    inline string getText() const { return getNode().getText(); }
    // ...
};

然后,部分模板专门化用于处理包装一个const Accessor的情况,以表示它将接收 const Node&

Then, a partial template specialization for handling the case of wrapping a "const Accessor", which is how to signal that it will be receiving a const Node &:

template<class AccessorType>
class Wrapper<AccessorType const> {    
protected:
    AccessorType accessorDoNotUseDirectly;
private:
    inline AccessorType const & getAccessor() const {
        return accessorDoNotUseDirectly;
    }

public:
    Wrapper () = delete;
    Wrapper (Node const & node) { getAccessor().setNodePtr(&node); }
    AccessorType const * operator-> const () { return &getAccessor(); }
    virtual ~Wrapper () { }
};

mutable Accessor案例的包装器继承自己的部分模板特化。这种方式继承提供了正确的强制和赋值...禁止将const赋值给非const,但是以其他方式工作:

The Wrapper for the "mutable Accessor" case inherits from its own partial template specialization. This way the inheritance provides for the proper coercions and assignments...prohibiting the assignment of a const to a non-const, but working the other way around:

template<class AccessorType>
class Wrapper : public Wrapper<AccessorType const> {
private:
    inline AccessorType & getAccessor() {
        return Wrapper<AccessorType const>::accessorDoNotUseDirectly;
    }

public:
    Wrapper () = delete;
    Wrapper (Node & node) : Wrapper<AccessorType const> (node) { }
    AccessorType * operator-> () { return &Wrapper::getAccessor(); }
    virtual ~Wrapper() { }
};

具有测试代码和注释记录奇怪部分的编译实现在此处的Gist 中。

A compiling implementation with test code and with comments documenting the weird parts is in a Gist here.

资料来源: @MatthieuM。 @PaulGroke

这篇关于派生自其实例存在于固定格式(数据库,MMF)的基类...如何安全?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆