首页 > 解决方案 > 使用引用计数智能指针的前向声明

问题描述

我已经实现了一个类,该类reference<T>跟踪对Treference_countable.

我有一个关于转发声明的T问题reference<T>,类似于std::unique_ptr<T>. std::unique_ptr<T>问题来自于不知道的析构函数,因此您只需将类的析构函数放入 cpp 文件中,如下所示:

标题:

class MyClass;
class A
{
    std::unique_ptr<MyClass> my_class;
}

执行:

A:~A() = default;

但是,在我的版本中, wherestd::unique_ptr<MyClass>替换为reference<MyClass>reference_countable也必须递减和递增。这要求我在仅前向声明时也将复制分配和复制构造函数A放入 cpp 文件中。MyClass

有没有办法避免将这三个函数的实现放在所有具有reference<T>成员的类的 cpp 文件中?


我试图尽可能简单地描述它,但要了解更多细节,这里是问题的简单版本。具体来说,需要A在a.cpp中定义copy-constructor。

my_class.h

#pragma once
#include "minimal_ref_counter.h"

class MyClass : public minimal_reference_countable
{
};

#pragma once
#include "minimal_ref_counter.h"

class MyClass;

class A
{
public:
    A();
    ~A();
    A(const A&);
    minimal_reference_counter<MyClass> my_class;
};

a.cpp

#include "test.h"
#include "myclass.h"

A::A() 
    : my_class(new MyClass())
{}

A::~A() = default;

A::A(const A&) = default;

some_other_code.cpp

#include "a.h"

void some_function()
{
    A a1;
    A a2 = a1; // this code does not compile without the copy assignment operator beeing implemented externally.
}

minimum_ref_counter.h

#pragma once
#include <atomic>

class minimal_reference_countable
{
    template<typename T>
    friend class minimal_reference_counter;

    std::atomic_int m_references = 0;

    auto reference_count() const { return m_references.load(); }

    void decrement() { --m_references; }
    void increment() { ++m_references; }
};

template<typename T>
class minimal_reference_counter
{
public:

    minimal_reference_counter(T* t = nullptr)
    {
        assign(t);
    }

    ~minimal_reference_counter()
    {
        reset();
    }

    minimal_reference_counter(const minimal_reference_counter& r)
    {
        *this = r;
    }

    minimal_reference_counter(minimal_reference_counter&& r)
    {
        *this = std::move(r);
    }

    minimal_reference_counter& operator=(const minimal_reference_counter& r)
    {
        assign(r.m_ptr);
        return *this;
    }

    minimal_reference_counter& operator=(minimal_reference_counter&& r)
    {
        assign(r.m_ptr);
        r.reset();
        return *this;
    }

    void reset()
    {
        if (!m_ptr) return;
        m_ptr->decrement();
        if (m_ptr->reference_count() == 0) 
        {
            delete m_ptr;
        }
        m_ptr = nullptr;
    }

private:

    void assign(T* ptr)
    {
        reset();
        m_ptr = ptr;
        if (m_ptr) m_ptr->increment();
    }

    T* m_ptr = nullptr;
};

标签: c++smart-pointersforward-declarationtype-erasure

解决方案


C++ 模板是惰性的。实例化被推迟到必要时。当你有一个不完整类型的成员,稍后完成时,将析构函数放入 cpp 文件的原因是std::unique_ptr析构函数需要 的析构函数unique_ptr,从而实例化它,这需要指向类型的析构函数,它需要该类型要完整。由析构函数的定义触发的这个依赖链必须推迟到指向类型完成时。

在您的引用计数指针的情况下,您想知道指针的复制构造函数、复制赋值运算符和析构函数是否一定需要指向类型的完整性,并且同样它们的实例化被推迟到指向类型完成的时间。

因为指针析构函数可能调用指向类型的析构函数,所以在实例化指针析构函数时该类型必须是完整的。这类似于unique_ptr,因此应该不足为奇。

类似地,指针复制赋值可能会调用指向类型的析构函数。它用另一个指针替换指针,并减少原始指针的引用计数,并在必要时将其销毁。因此,出于与析构函数相同的原因,指针复制赋值运算符要求指向的类型在实例化时必须是完整的。

复制构造函数更加微妙。不会调用指向类型的潜在破坏。然而,在这个实现中,我们作用于指向类型的基类来增加引用计数。这需要完整性,否则就没有基类。所以,通过这个实现,是的,当复制构造函数被实例化时,指向的类型必须是完整的。

当然,还有其他实现。可以同时保留 aT*和 a minimal_reference_counter<T>*,而不是从前者中找到后者作为基类。shared_ptr做这个。事实上,也可以将析构函数存储为函数指针,而不需要其他笨拙的实例化延迟。shared_ptr也这样做。


推荐阅读