C ++ Puzzle:防止派生类的堆分配,允许自动和静态

时间:2009-07-15 18:09:59

标签: c++

目的:

  1. 类Base的对象可以是静态的,自动的,直接在堆上分配,并参与任何地方分配的复合对象
  2. 对于任何具有Base作为可访问祖先的Derived类,对象可以是静态的或自动的并参与复合,但 可以直接在堆上分配
  3. 示例:

    #include "Base.h"
    #include "Derived.h"
    
    {
        static Base sb;              // OK
        Base ab, *hb = new Base;     // OK
        static Derived sd;           // OK
        Derived ad;                  // OK
        Derived *pd = &ad;           // OK
        Derived *hd = new Derived;   // Compile error, link error, 
                                     // test-unit exception, or lint gripe
        struct Composite {
            Base cb;
            Derived cd;
        } *hc = new Composite;       // OK 
    
        // Edit to show side-effects of solutions which hide Base::operator new.
    
        std::vector<Base> vb;        // OK 
        std::vector<Derived> vd;     // Error
        // ...
    }
    

    如何实现Base来实现这一目标?编译时错误优先于链接时错误;但是虽然两者都优于测试单元异常,并且测试单元异常优于lint gripe,但任何不需要为每个派生更新Base.h的解决方案都可以。

    编辑:出于这个问题的目的,尽管存在技术挑战,但涉及分配编译器以使其支持任意装饰的解决方案被归类为“微不足道”。

5 个答案:

答案 0 :(得分:5)

嗯,Eclipse的答案已经消失,但我认为它是在正确的轨道上。

class Base {
public:
    static Base *create() { return new Base; }
    static Base *create(size_t n) { return new Base[n]; }
private: 
    // Prevent heap allocation
    void *operator new(size_t s);
    void *operator new[](size_t s);
};

这不是很好,因为它必须使用Base::create()代替new Baseclass Derived仍然可以实现自己的public operator new {{1}} 1}},但我认为这是可行的。

答案 1 :(得分:4)

我在这里窃取ephemient's雷声感觉很糟糕,但他回答的唯一“错误”是他让Base的操作员成为私有的,而不是Derived's:

以下代码编译除了最后一行,我认为这是你需要的:

#include <new>
#include <vector>

class Base {};

class Derived : public Base {
private:
  void * operator new (size_t);
};

void foo ()
{
    static Base sb;              // OK
    Base ab, *hb = new Base;     // OK
    static Derived sd;           // OK
    Derived ad;                  // OK
    Derived *pd = &ad;           // OK

    struct Composite {
        Base cb;
        Derived cd;
    } *hc = new Composite;       // OK 

    std::vector<Base> vb;        // OK 
    std::vector<Derived> vd;     // OK


    Derived *hd = new Derived;   // Compile error
}

<强>更新

正如Tal指出的那样,你仍然可以从Derived的静态成员中调用“new Derived”,但是如果没有定义“operator new”,这将导致链接错误。

但您可以稍微更改代码,以便生成编译错误(这总是更可取)。我们可以声明一个新的贴牌操作符,它仍然会阻止我们调用普通的操作符new。

class Derived : public Base {
public:
  static Derived * create ()
  {
    return new Derived;
  }

private:
  class dont_dynamically_allocate_type_derived;
  void * operator new (size_t, dont_dynamically_allocate_type_derived);
};

使用g ++,上面生成:

  

t.cc:在静态成员函数中静态Derived * Derived :: create():

     

t.cc:10:错误:没有匹配函数来调用Derived :: operator new(unsigned int)

     

t.cc:15:注意:候选人是:static void * Derived :: operator new(size_t,Derived :: dont_dynamically_allocate_type_derived)

更新(部分期货):

我想不出基类传播它本身不具有的属性的任何构造。略微调整需求,如果允许添加额外的继承层,则可以创建两个叶类,一个用于Base,用于实例化Base对象的类型,另一个用于继承:

class Base
{
public:

private:
  friend class BaseInstance;
  friend class BaseDerived;
  ~Base () {}
};

class BaseInstance : public Base
{
public:
  ~BaseInstance () {}
};

class BaseDerived : public Base
{
public:
  ~BaseDerived () {}

private:
  static void * operator new (size_t);
  static void * operator new[] (size_t);
};

class Derived : public BaseDerived {
public:
  static Derived * create ()
  {
    return new Derived;
  }
};

仍有一个问题,人们可以从“BaseInstance”派生出来,虽然它的名字可以明确地用来阻止这种情况发生。还有可能该类位于API的另一端,因此客户端只能看到“BaseDerived”。

答案 2 :(得分:0)

这是我在网上找到的一个解决方案:

另一个标准的新的,即所谓的“placement-new”,根本不分配内存,但可以调用它来调用任意内存块上的对象构造函数。它通常定义为:

inline void *
operator new(size_t, void *p)
{
  return p;
}

来自http://www.scs.stanford.edu/~dm/home/papers/c++-new.html

答案 3 :(得分:0)

虽然有很多方法可以“阻止”代码中的对象堆分配(这里讨论并链接了几个好的),但是没有办法完全阻止它。有一些类型,例如shared_ptr&lt; T&gt;。我通常从不想要堆分配(或者我真正从不希望堆分配的变体)。对于这些,绝对最佳的解决方案是理解并记录为什么你永远不希望它们分配堆。有了这个,我们从来没有遇到过问题,因为我们对此持一致。

答案 4 :(得分:0)

如何私下派生基地?

class Base {
public:
    void *operator new(size_t); 
};

class Derived : private Base {
public:
};