【问题标题】:Forward declaration with reference-counting smart pointer使用引用计数智能指针的前向声明
【发布时间】:2021-09-09 10:35:39
【问题描述】:

我已经实现了一个类 reference<T>,它跟踪对从 reference_countable 派生的 T 的引用量。

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

标题:

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

实施:

A:~A() = default;

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

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


我试图尽可能简单地描述它,但为了更详细,这里是问题的简单版本。具体来说,需要在a.cpp中定义A的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.
}

minimal_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;
};

【问题讨论】:

  • 我更好奇为什么你需要创建这个reference&lt;T&gt; 类?它解决了std::shared_ptr&lt;T&gt;没有解决什么问题?
  • 您误解了为什么析构函数进入 cpp 文件:它只是将实例化 std::unique_ptr::~unique_ptr(或实际上是 std::default_delete::operator())推迟到 MyClass::~MyClass 可见之后
  • 然而,如果你有一个reference_countable 基类,你不需要知道任何派生的MyClass 来使用它。为什么不在operator* 或其他地方存储一个基类指针并处理向下转换?
  • @Someprogrammerdude 目标是有一个更轻量级的 shared_ptr 版本(甚至可能有一个非线程安全的版本)。
  • @Useless 前向MyClass 不知道有reference_countable 基类。所以不能调用递减的增量。我必须存储 2 个指针,指向 MyClass 和一个指向 reference_countable,当唯一的目标是不必将复制分配/构造函数放在 cpp 文件中时,我不想这样做。

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


【解决方案1】:

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

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

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

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

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

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

【讨论】:

    猜你喜欢
    • 2011-06-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-30
    • 2017-03-20
    • 2020-04-19
    相关资源
    最近更新 更多