命名构造函数和new操作符

Named constructor idiom and new operator

本文关键字:new 操作符 构造函数      更新时间:2023-10-16

我使用命名构造函数习惯来创建对象,因为我有许多具有相同参数的调用,但对象将以不同的方式创建。

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

现在Allocatorpoint_tfriend。所以它可以访问它的private构造函数。此外,您可以在Allocator中添加更多的构造函数,如<A1, A2>,以使其更加一般化。优点是:

  1. 你不必担心编译器优化
  2. friend船没有被利用,Allocatortemplate船并且仅用于堆分配

演示。