【问题标题】:C++ class method, returns vector<subclass>C++ 类方法,返回向量<子类>
【发布时间】:2011-10-12 03:19:06
【问题描述】:

我在尝试为班级编写的方法时遇到了一些问题。我有班级符号和班级终端。类终端扩展类符号,但类符号的方法之一需要返回一个向量。例如:

#ifndef SYMBOL_H
#define SYMBOL_H

#include "terminal.h"
#include <vector>

using namespace std;

class symbol {
   public:
        vector<terminal> first();
        virtual void polymorphable();
};

#endif

定义了类终端:

#ifndef TERMINAL_H
#define TERMINAL_H

#include "symbol.h"

using namespace std;

class terminal: public symbol {
    // ...
};

#endif

但是,这样做时,我在构建时遇到两个错误,其中一个首先出现:定义向量函数的行上的“'terminal':未声明的标识符”,以及“'symbol':未定义的基类" 与终端类定义一致。

如何解决“a 需要 b”、“b 需要 a”的问题?

【问题讨论】:

    标签: c++ stl polymorphism


    【解决方案1】:

    使用 Forward Declarations 避免循环依赖。

    class terminal;
    
    class symbol
    {
      std::vector<terminal> first();
      // ...
    };
    

    根据 C++ 标准,有人猜测这种方法是未定义的。
    @Ben Voight 指出:

    C++03 标准第 17.6.4.8 节说:

    “特别是,在以下情况下效果是未定义的:...如果将不完整的类型用作模板参数在实例化模板组件时,除非该组件特别允许.. .

    std::vector&lt;X&gt; f(); 是否为std::vector&lt;X&gt; 实例化,正在讨论here。如果那里的答案证明是这样,那么这个答案不成立,我将删除它,否则它仍然有效。

    【讨论】:

    • adp-gmbh.ch/cpp/forward_decl.html 只是一个很好的参考,因为没有提供示例,并且大多数使用指针而不是具体对象来显示它:)
    • @JamesMcNellis:当它只是函数声明中的返回类型时,它如何导致std::vector实例化?请注意解释一下。
    【解决方案2】:

    编辑: 标准可能不允许以下内容(参见 cmets)。在这种情况下,您根本无法拥有适当的循环依赖:如果A 的大小取决于B 类型成员的大小,但B 的大小取决于@ 类型成员的大小987654325@,那么这样的定义根本没有意义。

    不过,我不完全确定这是否适用于您的情况,因为您只声明了一个返回类型不完整的函数, 允许这样做。见服务员question by James;希望我们能在那里得到一个明确的答案。


    只需转发声明terminal:

    class terminal;
    
    class symbol
    {
      std::vector<terminal> first();
      // ...
    };
    

    您可以前向声明任何只需为 incomplete 类型的内容。不完整类型可用于形成指针、引用、函数签名。只有当该类型的变量被使用时,才需要知道完整的类型。

    【讨论】:

    • 我不认为使用不完整类型作为std::vector 的模板参数是正确的。
    • 你确定吗?我刚刚尝试过,它可以工作,但我不完全确定标准...vector 只使用T*,不是吗?
    • @KerrekSB: 不,vector 还需要调用T 的构造函数和析构函数。这是未定义的行为,因此它可能看起来有效,但它不是正确
    • @Ben:仅当您实例化 symbol 类时。我们只是在这里定义它。
    • 另外请注意,我们只是在声明一个函数,函数参数和返回类型可能不完整。
    【解决方案3】:

    Base 类不需要知道任何关于Derived 类的信息。这是面向对象设计中的一个重要原则。您可能会将基类更改为:

    class symbol {
      public:
        vector<symbol*> first();
        virtual void polymorphable();
    };
    

    现在,first() 返回一个指向基类的指针向量。使用多态性,每个指针实际上都可以指向派生类。请注意,我将其更改为使用指针。如果您将其更改为简单的vector&lt;symbol&gt;,那将无法正常工作。

    或者,如果你真的需要基类知道派生类的存在,你可以转发声明派生类:

    #ifndef SYMBOL_H
    #define SYMBOL_H
    
    #include "terminal.h"
    #include <vector>
    
    using namespace std;
    class terminal;  // forward declare the existence of this class
    
    class symbol {
      public:
        vector<terminal*> first();     // change to be a vector of pointers
                                       // to avoid issues with incomplete type
        virtual void polymorphable();
    };
    
    #endif
    

    【讨论】:

    • 您或许应该使用 unique_ptr 而不是原始指针。注意,避免使用带有 STL 集合的 auto_ptr。
    【解决方案4】:

    使用前向声明。

    James - 注意声明与实例不同。此代码运行良好

    #include <vector>
    
    class terminal; <--- TELLING THE COMPILER MAY USE terminal in the future.
    
    class symbol 
    {        
      std::vector<terminal> first(); <--- NOTE THE COMPILER DOES NOT NEED TO KNOW HOW TO CONSTRUCT EITHER
      // ... 
    };           
    
    class terminal: public symbol  < --- TELLS COMPILER THAT terminal INHERITS symbol i.e. CONTAINING THE METHOD first
    {
       int wibble; 
    };  
    
    int main()
    {
        symbol s;
        return 0;
    }
    

    Als - 你是对的。

    【讨论】:

    • 第 17.6.4.8 节说你错了。或者您可以引用std::vector 明确允许使用不完整类型的标准吗?
    • 我使用g++ (4.3.1) 编译了它。 first 函数声明只是说它将返回一个vector&lt;terminal。只是一个声明。当您执行first 时,编译器将知道symbolterminal
    • 使用具有特定设置的特定编译器编译代码没有错误的事实并不意味着代码是正确的。我认为std::vector&lt;terminal&gt; 这里会导致std::vector 类模板的实例化类型不完整,这会导致程序表现出未定义的行为(我找不到说明函数声明不会导致实例化的规范文本;我可能错了)。
    • 詹姆斯等人。去尝试一下。您在声明和实现之间感到困惑。我不需要任何特殊的开关来编译它。
    • @EdHeal: "编译" != "定义明确的行为"。
    【解决方案5】:

    我认为 Curiously Recurring Template Pattern 可以让你摆脱这个:

    template<typename terminal_type>
    class symbol_pattern
    {
       public:
            std::vector<terminal_type> first();
            virtual void polymorphable();
    };
    
    class terminal : public symbol_pattern<terminal>
    {
    };
    
    typedef symbol_pattern<terminal> symbol;
    

    【讨论】:

    • 有点矫枉过正,你不觉得吗?
    • @KerrekSB,不,我认为你的回答有点矫枉过正。
    • 这听起来可能很刺耳,但很少有人会读到足够多的关于模板元编程的知识来接触这种设计模式。它只会让下一个必须维护它的人感到困惑 - 特别是在一些人添加到它之后:(
    • 嗯,我很糟糕,我知道该模式是为了在编译时实现静态多态而不是在运行时实现动态多态,我想我做了一个糟糕的逻辑飞跃。不过,我仍然认为这是解决常见问题的一个非常复杂的解决方案。也许只是我不完全理解它:)
    • 这不是和其他使用termimal前向声明的例子有同样的问题吗?当symbol_pattern 被实例化为terminal 的基类时,terminal 仍然是不完整的。所以,terminal_type(即terminal)在用于实例化std::vector&lt;terminal_type&gt;时是不完整的。
    【解决方案6】:

    与其让单个成员函数返回容器,不如考虑让 begin()end() 函数返回一个迭代器范围,类似于标准库容器本身所做的:

    class terminal;
    
    class terminal_iterator { /* defined appropriately */
    
    struct symbol
    {
        terminal_iterator begin_terminals() const;
        terminal_iterator end_terminals() const;
    };
    

    如果您在某个地方已经有一个std::vector&lt;terminal&gt; 来进行迭代,您可以只使用typedef terminal const* terminal_iterator;(或使用类似的typedef)并相应地定义成员函数。

    如果您没有容器(即,成员函数将实现序列本身),您可以考虑编写自己的迭代器类来生成序列。

    提供begin()end() 范围访问器有时比简单地为容器提供访问器需要更多的工作,但范围访问器提供了更大的灵活性和抽象性。

    【讨论】:

    • 不过,这会导致生命周期问题。
    • @BenVoigt:生命周期约束不同,但并不是特别困难。
    【解决方案7】:

    前向声明 + 智能指针(尽管现在这会将东西存储在堆而不是堆栈上......可能是不可取的)

    #ifndef SYMBOL_H
    #define SYMBOL_H
    
    #include <vector>
    #include <memory>
    
    using namespace std;
    
    class terminal; // Make a forward declaration like this
    
    class symbol {
       public:
            vector<shared_ptr<terminal>> first();
            virtual void polymorphable();
    };
    
    #endif
    

    【讨论】:

      猜你喜欢
      • 2012-05-17
      • 2014-09-10
      • 2016-01-12
      • 2021-12-25
      • 2012-03-03
      • 1970-01-01
      • 1970-01-01
      • 2016-02-01
      • 2011-04-25
      相关资源
      最近更新 更多