【问题标题】:C++ forward decleration of class in namespace with smart pointers具有智能指针的命名空间中的类的 C++ 前向声明
【发布时间】:2019-11-12 16:25:29
【问题描述】:

由于与视频类的循环依赖关系,我正向前声明一个位于另一个命名空间内的类。此类用于存储在 mmu 类中的共享指针。一旦我在类的构造函数中分配了共享指针,它就会出错invalid use of incomplete type ‘using element_type = class graphic::Video {aka class graphic::Video}’

mmu.h

#pragma once
#include "../cartridge/cartridge.h"

/* forward decleration */
namespace graphic {
    class Video;
}

namespace memory {

    class Mmu {
        public:

            std::shared_ptr<Cartridge> cartridge;
            std::shared_ptr<graphic::Video> video;


            Mmu(std::shared_ptr<Cartridge>&, std::shared_ptr<graphic::Video>&);

    };
}

mmu.cpp

#include <iostream>
#include "mmu.h"

namespace memory {
    Mmu::Mmu(std::shared_ptr<Cartridge> &_cartridge, std::shared_ptr<graphic::Video> &_vid)
    : video(_vid)
    {
        video->initializeColorPallete(); //errors
    }

调用者.cpp

#include <iostream>
#include <vector>
#include "caller.h"

Caller::Caller(std::vector<uint8_t> cartridgeData) 
    :cartridge(getCartridge(std::move(cartridgeData))),
     cpu(std::make_unique<cpu::CPU>(mmu)),
     video(std::make_shared<graphic::Video>(mmu, cpu)),
     mmu(std::make_shared<memory::Mmu>(cartridge, video))
    {

    }

video-&gt;initializeColorPallete(); 在构造函数中出错。

【问题讨论】:

  • 因为graphic::Video 在调用它的方法时仍然是一个不完整的类型。将实际类包含到您的 cpp 文件中。
  • 投票结束是一个错字。您从未将定义 Video 的文件包含在 mmu.cpp 中。
  • 除此之外,您还需要在 cpp 中为 std::shared_ptr&lt;graphic::Video&gt; 成员定义的类 Mmu 的析构函数。从声明生成的默认析构函数不知道如何析构graphic::Video 对象。
  • 用不同的话来说说:前向声明有助于打破头文件中的循环依赖关系,但仍然需要完整的定义才能使用这种类型。编译器如何知道graphic::Video 类型是否有方法initializeColorPallete,它需要多少个参数以及它的返回类型是什么。
  • @parktomatomi 谢谢,但你并没有真正回答我的问题。尝试让班级成员 unique_ptr&lt;T&gt; 前向声明 T。至少对于当前的 GCC,如果没有声明的析构函数在已知 T 的某处定义(可以为空),它将无法编译(当类被实例化时)。使用shared_ptr&lt;T&gt;,令我惊讶的是,问题不存在。

标签: c++ smart-pointers forward-declaration


【解决方案1】:

所以,重申一下您的要求:

  • 你不知道Video的完整实现
  • 您需要在Mmu使用 Video
  • Video 需要一个 Mmu,直接或间接地创建一个循环类型依赖。

这非常适合纯虚拟界面。仅使用Mmu 需要的Video 的方法进行合约:

/* instead of a forward decleration */
namespace graphic {
    class IVideo {
    public:
        virtual void initializeColorPallete() = 0;
        virtual ~IVideo() = default; // always always always needed with interfaces
    };
}

因为这些是虚方法,IVideo 的使用者不需要知道有关完整类型的任何详细信息,因为顶部会有一个名为 vtable 的小函数指针表。

然后Mmu 将采用IVideo

namespace memory {
    class Mmu {
        public:

            std::shared_ptr<Cartridge> cartridge;
            std::shared_ptr<graphic::IVideo> video;
            Mmu(std::shared_ptr<Cartridge>, std::shared_ptr<graphic::IVideo>);
    };
}

Video实现 IVideo:

// Mmu doesn't need to know anything about me
class Video : public graphic::IVideo {
    void initializeColorPallete() override { /* doin' its thing */ }
};

您可以将Video 的智能指针传递给MmuIVideo 输入:

std::shared_ptr<graphic::IVideo> my_video = std::make_shared<Video>(/*...*/);

因为IVideo 的析构函数是虚拟的,shared_ptr 可以安全地删除它,而无需了解更多关于实现的信息。

另一个小技巧:不要对智能指针使用左值引用。 shared_ptr 依赖于值类型的复制来计算其依赖关系。与unique_ptr 相同,需要移动才能转移。

另一个小提示:使用 shared_ptr 循环可能有点危险。如果您在循环中的某个时刻传递weak_ptr,这将防止内存泄漏。一般来说,对于所有权,更喜欢unique_ptr,对于非所有权,更喜欢哑指针/引用,但这是一个不同的争论。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-12-27
    • 1970-01-01
    • 2011-06-14
    • 1970-01-01
    • 2012-12-15
    • 2011-09-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多