我可以使用 STL 容器管理不完整的 class 对象吗?

Can I manage incomplete class objects using STL containers?

在 C++11 中,声明一个 class 的向量肯定是无效的,它仍然是一个不完整的类型,对吧?我以为我只能使用不完整的类型作为指针、引用、return 类型或参数类型。在 (1) 中搜索 "vector of incomplete type" 向我建议不完整类型的容器应该是错误的(我使用的是 g++ 版本 4.8.1。)。然而,以下代码在我的系统上编译得很好:

#ifndef SCREEN
#define SCREEN
#include <string>

using namespace std;

class Screen;

class Window_mgr{
public:
    typedef vector<Screen>::size_type screenindex;
    void clear(screenindex);
private:
    vector<Screen> screens;
};

class Screen{
    friend void Window_mgr::clear(screenindex);
public:
    typedef string::size_type pos;
    Screen() = default;
    Screen(pos ht, pos wt): height(ht), width(wt), contents(ht*wt, ' ') { }
    Screen(pos ht, pos wt, char c): height(ht), width(wt), contents(ht*wt, c) { }

private:
    pos height = 0, width = 0;
    pos cursor = 0;
    string contents;

};

inline void Window_mgr::clear(screenindex i){
    Screen& s = screens[i];
    s.contents = string(s.height*s.width, ' ');
}

#endif // SCREEN

尽管 Window_mgr 声明了一个 Screens 向量,它仍然是一个不完整的类型。我示例中的这些 classes 实际上基于 C++ Primer 的第 7.3 章。一个问题要求我定义我自己的 Screen 和 Window_mgr 版本,其中成员函数 clear 是 Window_mgr 的成员和 Screen 的友元。除了 Window_mgr 还意味着包含屏幕向量。

如果创建一个不完整类型的向量是无效的,我该如何使用前向声明来做到这一点?如果我在 Window_mgr 中有一个屏幕矢量,那么它的 class 定义必须在 class 屏幕已经定义之后。除了Screen must have a friend declaration of the clear member function of Window_mgr,但是下面的重排是错误的,因为Screen在一个不完整的类型上使用了作用域操作符;

class Window_mgr;

class Screen{
    friend void Window_mgr::clear(screenindex);
public:
    typedef string::size_type pos;
    Screen() = default;
    Screen(pos ht, pos wt): height(ht), width(wt), contents(ht*wt, ' ') { }
    Screen(pos ht, pos wt, char c): height(ht), width(wt), contents(ht*wt, c) { }

private:
    pos height = 0, width = 0;
    pos cursor = 0;
    string contents;

};

class Window_mgr{
public:
    typedef vector<Screen>::size_type screenindex;
    void clear(screenindex);
private:
    vector<Screen> screens;
};

inline void Window_mgr::clear(screenindex i){
    Screen& s = screens[i];
    s.contents = string(s.height*s.width, ' ');
}

我能想到的唯一方法是用 class

的友元声明替换成员函数友元声明
class Screen{
friend class Window_mgr;
// other stuff
}

但这不是我想要的问题。

标准容器目前不支持不完整的类型;比照。 Can standard container templates be instantiated with incomplete types? - 也就是说,实现可以选择支持不完整的类型作为扩展,但这会使您的代码不可移植。

C++ 标准的论文 n4371 Minimal incomplete type support for standard containers, revision 2 has been incorporated into the most recent draft (n4527),因此除非出现意外情况,否则 vectorlistforward_list 很可能会支持不完整的类型在 C++17 中。


有一种方法可以满足 "C++ Primer" 中的要求,而不依赖于实现扩展或 C++17:

  1. clearWindow_mgr的成员函数;
  2. Window_mgr::clear()Screen 的朋友;
  3. Window_mgr 包含一个向量 Screens:

您可以使 Screen 成为 Window_mgr 的嵌套 class:

class Window_mgr{
public:
    typedef std::size_t screenindex;
    void clear(screenindex);

    class Screen{
        friend void Window_mgr::clear(screenindex);
    public:
        // ...
    };

// ...
private:
    vector<Screen> screens;
};

即使在这里,我也不得不调整类型 screenindex 的定义以打破依赖循环。

简短的回答是,使用不完整类型的向量是非法的。就像任何其他 STL 容器一样。某些东西可以编译甚至可以完美运行的事实并不意味着它是合法的。

您在 (1) references this 篇文章中提到的文章。这对此事给出了非常清楚(尽管很长)的解释。

按照我的解释,它归结为以下几点:

  • 一般来说,在不完整的类型上使用 STL 模板是非法的
  • 实际上它适用于一些 STL 容器和库
  • Vector 是您真正可以放心使用它的一种情况。我提到的 article 注释章节的最后一点指出:

But perhaps it should be relaxed on a case-by-case basis, and vector looks like a good candidate for such special-case treatment: it's the one standard container class where there are good reasons to instantiate it with an incomplete type and where Standard Library implementors want to make it work.

您描述的另一个问题很常见。为了简化它,您可以说以下内容。假设你有两个 类:

class Foo
{
   std::vector<Bar> myVector;
}

class Bar
{
   std::vector<Foo> myVector;
}

这似乎是一件合理的事情。但在标准的限制下,这简直是不可能的。根据标准解决这个问题的一种方法是使用指针列表(std::shared_pointer 在这里很有用)。或者,放弃不遵守标准并使用前向声明。对于几乎任何编译器,这都可以工作。