【问题标题】:I am attempting to define a class which can only be instantiated on the heap我正在尝试定义一个只能在堆上实例化的类
【发布时间】:2018-08-08 12:30:19
【问题描述】:

我试图通过将删除运算符重载为友元函数来实现这一点。代码如下:

#include <iostream>


class HeapOnly
{
    int x;
    int y;
    ~HeapOnly()
    {

    }
public:
    void friend operator delete(void* a);
};

void operator delete(void* p)
{
    delete p;
}

int main()
{
    HeapOnly *abc = new HeapOnly();
    delete abc;
}

以上代码在 Visual Studio 中给出错误“Error C2248 'HeapOnly::~HeapOnly': cannot access private member declaration in class 'HeapOnly”

我不明白为什么它会出错,因为删除运算符作为友元函数重载,因此它应该能够访问私有的析构函数。

【问题讨论】:

  • 为什么不希望这种类型的对象具有任何其他存储持续时间?
  • 您是否考虑过私有构造函数,使用返回指针的静态方法获取新实例?另外,你究竟为什么需要这样的课程?
  • “我正在尝试定义一个只能在堆上实例化的类” - 我不明白为什么,但是如何将所有构造函数设为私有并提供在堆上创建实例的工厂静态成员函数?
  • delete 不是运算符,与重载实际运算符时不同,delete abc 不是调用operator delete 的语法糖。 operator delete 有一个令人困惑的名称,更适合称为“deallocate”或“free”或类似的名称,但“C++ 方式”是重载现有保留字的含义,而不是引入新保留字。

标签: c++ visual-studio


【解决方案1】:

实际上并没有一个“删除运算符”可以让您重载。有:

  • 删除表达式,如delete abc;和
  • 释放内存的释放函数,其语法令人困惑的是operator delete

换句话说,您从未重载不存在的“删除运算符”或将其声明为朋友。唯一重载并声明为友元的是释放函数

删除表达式分两步完成:

  1. 调用析构函数将对象还原为原始内存
  2. 调用释放函数来释放内存

因此,私有析构函数仍然被认为是由main 直接调用的,它不是类的朋友。

我们可以做一个小实验来验证:

代码 1:没有 friend 声明:https://godbolt.org/g/WVmzNP

class HeapOnly
{
    int x;
    int y;
    ~HeapOnly() = default;
};

int main()
{
    HeapOnly *abc = new HeapOnly();
    delete abc;
}

代码 2:声明 main 为好友:https://godbolt.org/g/9PVZ4C

class HeapOnly
{
    int x;
    int y;
    ~HeapOnly() = default;

    friend int main();
};

int main()
{
    HeapOnly *abc = new HeapOnly();
    delete abc;
}

【讨论】:

    【解决方案2】:

    关于您的尝试失败的原因,请参阅其他答案。我不知道您是否以及如何实现只能在堆上实例化的类的总体目标,以及这是否可取,但我怀疑两者。

    但是,您可以做的是限制取消分配通过std::allocatorstd::default_delete,这样简单的自动变量是不可能的:

    #include <iostream>
    #include <memory>
    #include <vector>
    #include <algorithm>
    
    class HeapOnly
    {
        ~HeapOnly();
      public:
        bool operator < (HeapOnly const&) const;
        friend class std::allocator<HeapOnly>;
        friend class std::default_delete<HeapOnly>;
    };
    
    int main()
    {
        std::vector<HeapOnly> X(10);   // okay
        HeapOnly Y;                    // ERROR: cannot be destructed
        std::sort(X.begin(),X.end());  // ERROR: requires automatic variables
        std::vector<std::unique_ptr<HeapOnly>> Z(10);    // okay
        for(auto&z:Z)
            z.reset(new HeapOnly);
        std::sort(Z.begin(),Z.end(),[](std::unique_ptr<HeapOnly> const&x,
                                       std::unique_ptr<HeapOnly> const&y)
                                    { return *x < *y; }); 
    }
    

    但是请注意,很多事情都依赖于自动变量,包括std::swapstd::sort(直接与std::sort 成为朋友并没有帮助,因为它将工作委托给特定于实现的帮助程序)。但是,您可以启用unique_ptr(和shared_ptr),这似乎与仅堆类有关,并对指向的对象进行排序。

    进一步注意,尽管上面的代码可能适用于大多数(如果不是全部)STL 实现,但不能保证。例如,std::allocator&lt;&gt;::destroy() 可以将销毁委托给辅助函数,这是一个实现细节,因此不能成为朋友。

    但是,您可以对此进行测试并使用 SFINAE 来规避此类情况。

    【讨论】:

    • @GauravSehgal Ummm... 只允许std::allocator&lt;HeapOnly&gt; 调用析构函数,而没有其他人?
    • 只是这个解决方案不允许使用带有newdelete 的“原始”指针。当然最好避免它们,但我们不知道 OP 是否需要它们。
    • @DanielLangr Ermh。好吧,我想我在一开始就说过,我不知道是否/如何实现这一点(原始指针但仅限堆)。
    • 您的friend 方式不可移植,这些实现可能使用辅助类/函数。
    • @Jarod42 我也害怕这个。但是你确定吗? AFAIK,allocator 不使用辅助函数。我不知道default_delete,但如果你能给我提供一个有用的证明(引用标准和/或cppreference)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-27
    • 2019-01-29
    • 1970-01-01
    • 2016-04-19
    相关资源
    最近更新 更多