命名构造函数idiom和new运算符

时间:2011-08-10 12:32:36

标签: c++

我使用命名的构造函数idiom来创建对象,因为我有很多具有相同参数的调用但是对象的创建方式不同。

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

是否有一个更漂亮的版本等于示例代码?

5 个答案:

答案 0 :(得分:13)

你可以完全避免使用命名构造函数idiom,并使用additonal dummy 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;
}

答案 1 :(得分:8)

你可以写:

point_t *x = new point_t(point_t::carthesian(1,2));

首先调用carthesian()然后调用复制构造函数。

或者,它有什么问题吗?也许,有点慢?

顺便说一句,这段代码有一个明显的优势:程序员可以在他的代码中清楚地看到new运算符(他使用其他人撰写的point_t,因此您可以假设他的责任在完成delete后致电x

答案 2 :(得分:4)

这真的是个问题吗?根据我的经验,课程往往是大部分时间动态分配,或者很少,如果有的话。表示值的类(例如此处的point_t类)属于第二类,而表示实体的类(即具有标识的类)属于第一类。

所以我的建议是选择你认为每个班级最好的方法并且只提供它。请注意,您始终可以返回一个小的直接分配的对象,该对象具有指向较大的对象的私有指针,如the Handle-Body idiom中所示。

另一方面,其他答案显示了如何消除带有相同数量和类型参数的构造函数的歧义。在这种思路中,一种替代方法是为参数引入特定类型,如下所示:

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

答案 3 :(得分:0)

我还没有看到的一种方法是重载构造函数,使得堆分配使用最后一个参数作为out(假设第二个函数在技术上不是构造函数,它不会返回一个实例)。结果就像(作为你的第二个代码片段的基础):

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

答案 4 :(得分:-1)

可以想到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);
}

现在Allocatorfriend的{​​{1}}。所以它可以访问它的point_t构造函数。此外,您可以在private中添加更多构造函数,例如<A1, A2>,以使其更加generalized。优点是:

  1. 看起来并不详细。
  2. 您不必担心编译器优化
  3. Allocator船只未被利用,friendAllocator 我们仅将其用于堆分配
  4. Demo