命名构造函数和new操作符
Named constructor idiom and new operator
我使用命名构造函数习惯来创建对象,因为我有许多具有相同参数的调用,但对象将以不同的方式创建。
c++ FAQ告诉我们如何做到这一点。它还告诉我们如何强制对象被堆分配。然而,它确实没有告诉我们如何将命名构造函数的习惯用法与new操作符一起使用。
因为new需要调用构造函数,所以不能直接调用命名构造函数。所以我找到了两个解决这个问题的方法:
我创建了一个额外的复制构造函数,希望优化编译器不会创建一个临时对象。
class point_t {
int X,Y;
point_t(int x, int y) : X(x), Y(y) { }
public:
point_t(const point_t &x) : X(x.X), Y(x.Y) { }
static point_t carthesian(int x, int y) { return point_t(x,y); }
static point_t polar(float radius, float angle) {
return point_t(radius*std::cos(angle), radius*std::sin(angle));
}
void add(int x, int y) { X += x; Y += y; }
};
int main(int argc, char **argv) {
/* XXX: hope that compiler doesn't create a temporary */
point_t *x = new point_t(point_t::carthesian(1,2));
x->add(1,2);
}
另一个版本是创建单独的命名构造函数。因为函数重载在返回类型上不起作用,所以我使用了两个不同的名称,这很难看。
class point_t {
int X,Y;
point_t(int x, int y) : X(x), Y(y) { }
public:
/* XXX: function overloading doesn't work on return types */
static point_t carthesian(int x, int y) { return point_t(x,y); }
static point_t *carthesian_heap(int x, int y) { return new point_t(x,y); }
void add(int x, int y) { X += x; Y += y; }
};
int main(int argc, char **argv) {
point_t *x = point_t::carthesian_heap(1,2);
x->add(1,2);
}
是否有一个更漂亮的版本,等于示例代码?
您可以完全避免命名构造函数的习惯用法,并使用额外的虚拟enum参数来选择构造函数。
enum Carthesian {carthesian};
enum Polar {polar};
class point_t {
int X,Y;
public:
point_t(int x, int y) : X(x), Y(y) { } // may keep as a default
point_t(Carthesian, int x, int y) :X(x),Y(y){}
point_t(Polar, float radius, float angle)
: X (radius*std::cos(angle)), Y(radius*std::sin(angle)) {}
void add(int x, int y) { X += x; Y += y; }
};
int main(int argc, char **argv) {
point_t *x = new point_t(carthesian,1,2);
point_t *y = new point_t(polar,0,3);
x->add(1,2);
}
它简单,可移植,您将看到的唯一开销是传递虚拟枚举值。在极少数情况下,这种开销对您来说太高了,即使构造本身没有内联,也可以通过封装函数调用来消除它,如下所示:
enum Carthesian {carthesian};
enum Polar {polar};
class point_t {
int X,Y;
void initCarthesian(int x, int y); // may be long, not inlined
void initPolar(float radius, float angle);
public:
point_t(int x, int y) : X(x), Y(y) { } // may keep as a default
point_t(Carthesian, int x, int y)
{initCarthesian(x,y);} // this is short and inlined
point_t(Polar, float radius, float angle) {initPolar(radius, angle);}
void add(int x, int y) { X += x; Y += y; }
};
另一种方法是使用派生类进行构造。当使用内部类时,我认为它会导致相当好的语法:
class point_t {
int X,Y;
public:
struct carthesian;
struct polar;
point_t(int x, int y) : X(x), Y(y) { } // may keep as a default
void add(int x, int y) { X += x; Y += y; }
};
struct point_t::carthesian: public point_t
{
carthesian(int x, int y):point_t(x,y){}
};
struct point_t::polar: public point_t
{
polar(float radius, float angle):point_t(radius*std::cos(angle),radius*std::sin(angle)){}
};
int main(int argc, char **argv) {
point_t *x = new point_t::carthesian(1,2);
point_t *y = new point_t::polar(0,3);
x->add(1,2);
return 0;
}
你可以这样写:
point_t *x = new point_t(point_t::carthesian(1,2));
首先调用carthesian()
,然后调用复制构造函数。
或者,有什么问题吗?也许,有点慢?
顺便说一下,这段代码有一个明显的优势:程序员可以清楚地看到new
运算符在他的代码中(在他使用别人写的 point_t
),所以你可以假设它的是他的责任调用delete
一旦他完成了x
。
这真的是个问题吗?根据我的经验,大多数情况下,类要么是动态分配的,要么是很少分配的。表示值的类,比如这里的point_t类,属于第二类,而表示实体(即具有身份的东西)的类属于第一类。
所以我的建议是为每个类选择你认为最好的方法,并只提供它。请注意,您始终可以返回一个直接分配的小对象,该对象具有指向较大对象的私有指针,如Handle-Body风格。
另一方面,其他答案显示了如何在接受相同数量和类型实参的构造函数之间消除歧义。按照这种思路,另一种方法是为参数引入特定类型,如下所示:
class radius_t {
float R;
public:
explicit radius_t(float r) : R(r) {}
operator float() const { return R; }
};
class angle_t {
float A;
public:
explicit angle_t(float a) : A(a) {}
operator float() const { return A; }
};
class point_t {
float X,Y;
public:
point_t(float x, float y) : X(x), Y(y) { }
point_t(radius_t radius, angle_t angle) :
X(radius*std::cos(angle)), Y((radius*std::sin(angle)) {
}
void add(int x, int y) { X += x; Y += y; }
};
int main(int argc, char **argv) {
point_t *x = new point_t(radius_t(1),angle_t(2));
x->add(1,2);
}
我还没有见过的一种方法是重载构造函数,使堆分配使用最后一个参数作为输出参数(假定第二个函数在技术上不是构造函数,它不返回实例)。结果将类似于(作为第二个代码片段的基础):
class point_t {
int X,Y;
point_t(int x, int y) : X(x), Y(y) { }
public:
/* XXX: function overloading doesn't work on return types */
static point_t carthesian(const int x, const int y) { return point_t(x,y); }
static void carthesian(const int x, const int y, point_t * & point) { point = new point_t(x,y); }
void add(int x, int y) { X += x; Y += y; }
void add(const point_t & point) { this->X += point.x; this->Y += point.y; }
};
int main(int argc, char **argv) {
point_t p1 = point_t::carthesion(1, 2);
point_t * p2;
point_t::carthesian(1, 2, p2);
p2->add(p1);
}
可以想到template
分配器:
template<typename T>
struct Allocator : T
{
template<typename A1, typename A2>
Allocator(A1 a1, A2 a2) : T(a1, a2) {}
};
class point_t {
//...
template<typename T> friend struct Allocator;
};
int main(int argc, char **argv) {
point_t *x = new Allocator<point_t>(1,2);
x->add(1,2);
}
现在Allocator
是point_t
的friend
。所以它可以访问它的private
构造函数。此外,您可以在Allocator
中添加更多的构造函数,如<A1, A2>
,以使其更加一般化。优点是:
- 你不必担心编译器优化
-
friend
船没有被利用,Allocator
是template
船并且仅用于堆分配
演示。
- 在重载的全局new操作符中使用静态对象会导致核心转储运行时错误
- 在c++中使用new操作符动态分配数组
- c++中不使用new操作符的内存泄漏
- c++中new操作符与java中new操作符的区别
- 在c++构造函数中使用new操作符是否正确?
- c++的new操作符保证返回的指针不会改变它的值吗?
- 如何使用QT命名空间中使用的重载new操作符
- 如何重载全局new操作符
- 如果new操作符重载且构造函数为私有,则无法通过new创建对象
- 如何使用new操作符创建对象并使用for循环
- 理解c++的new操作符
- 在覆盖new操作符时传递更多参数(c++)
- 使用new操作符在不知道对象类型的情况下将其复制到堆中
- 用new操作符定义std::shared_ptr时出错
- new操作符返回什么?
- 具有析构函数和new操作符的类
- 带有new操作符的异常和不带有new操作符的异常
- 内存管理:使用new操作符进行数组和动态分配
- c++中的定位new操作符是什么,java中有吗?
- 从指针向量中删除元素,并释放先前通过new操作符分配的动态内存