在C++中,为了让某个类只能通过new来创建(即如果直接创建对象,编译器将报错),应该( B )
A. 将构造函数设为私有
B. 将析构函数设为私有
C. 将构造函数和析构函数均设为私有
D. 没有办法能做到


参考:《More Effective C++》条款27:如何让类对象只在栈(堆)上分配空间?

在 C++ 中,类的建立分为两种,一种是静态建立,如:A a;;另一种是动态建立,如:A* p = new A();

静态建立类对象:由编译器为对象在栈空间中分配内存,通过移动栈指针,挪出适当的空间,然后在这片内存空间上调用构造函数形成一个栈对象。使用这种方法,直接调用构造函数。

动态建立类对象:使用 new 运算符将对象建立在堆空间中。首先执行 operator new() 在堆空间中搜索合适的内存并分配;然后调用构造函数构造对象,初始化这片内存空间。使用这种方法,间接调用类的构造函数。

让类对象只在堆上分配空间

也就是题目所说,让某个类只能通过 new 来创建。

首先容易想到将构造函数设为 private,但是使用 new 来创建类也要通过指针间接调用构造函数,所以这种方法不行,A 错误。

当对象建立在栈上时,由编译器分配内存空间并进行管理,对象使用完后,编译器需要调用类的析构函数来释放空间。如果编译器无法调用类的析构函数来释放内存,可能会造成内存泄漏。所以,编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性,其实只要是非静态的函数,编译器都会进行检查。如果类的析构函数在类外不可访问,则编译器不会在栈空间上为类对象分配内存。

因此,将析构函数设为 private,类对象就无法静态建立在栈上了,B 正确。

但是这样无法解决类的继承问题,如果类 A 作为其他类的基类,通常我们要将析构函数设为 virtual,然后子类重写析构函数,以实现多态。所以析构函数不能设为 private。我们可以将析构函数设为 protected 来解决继承问题,类外同样无法访问 protected,但子类可以访问。

还有一个问题是对 new 创建的对象,也无法通过 delete 释放对象,因为 delete 操作也是通过指针间接调用类的析构函数,所以要提供一个 public 函数 destory(),在其中完成析构函数,需要释放内存时即调用该函数。

1
2
3
4
5
6
7
class A {
public:
A(){}
void destory() { delete this; } // this->~A();
private: // protected:
~A(){}
};

但是这样以来,我们使用 new 操作符创建对象,调用函数释放对象,不太对称,既然无法通过 delete 释放对象,干脆也不用 new 了,同样提供一个 create() 函数,用来动态创建一个类对象,类似于单例模式

需要注意的是我们是需要用类名来调用 create() 函数来创建一个类对象并返回,用指针接收。所以 create() 函数应该设为 static 。同时将构造函数和析构函数一样也设为 protected,禁止通过 new 创建类对象。

这样,我们通过类名调用 create() 函数在堆上创建类 A 对象,调用 destory() 函数释放内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class A {
protected:
A(){}
~A(){}
public:
static A* create() { return new A(); }
void destory() { delete this; }
};

int main() {
A* pa = A::create();
pa->destory();
return 0;
}

让类对象只在栈上分配空间

只需要禁掉 new 就好了,虽然我们无法改变 new 操作符,但是 new 操作符会调用 operator new() ,而我们可以重写类 A 的 operator new()。实际上,只需要将其设置为 private 即可,同时也要将 delete operator 一起设置。

1
2
3
4
5
class A {
private:
void* operator new(size_t t) {} // 注意函数的第一个参数和返回值都是固定的
void operator delete(void* ptr) {} // 重载了new就需要重载delete
};