Подтвердить что ты не робот

Именованный конструктор идиом и новый оператор

Я использую именованную конструкторскую идиому для создания объектов, потому что у меня много вызовов с одинаковыми параметрами, но объект должен быть создан по-разному.

С++ FAQ расскажите, как это сделать. Он также сообщает нам, как принудительно распределять объекты, находящиеся в куче. Тем не менее, это действительно не позволяет нам сказать, как использовать именованную конструкторскую идиому с новым оператором.

Поскольку 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);
}

Есть ли более красивая версия, которая равна примерному коду?

4b9b3361

Ответ 1

Вы можете полностью исключить именованную конструкторскую конструкцию и сделать это, используя дополнительный параметр перечисления 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;
}

Ответ 2

Вы можете написать:

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

Сначала он вызывает carthesian(), а затем экземпляр-конструктор.

Или, есть ли в этом проблема? Возможно, немного медленнее?

Кстати, в этом коде есть одно явное преимущество: программист может четко видеть оператор new в своем коде (где он использует point_t, написанный кем-то другим), поэтому вы можете предположить, что его Ответьте на вызов delete, когда он закончил с помощью x.

Ответ 3

Это действительно проблема? В моем опыте классы обычно либо динамически выделяются большую часть времени, либо редко, если вообще. Классы, которые представляют значения, такие как ваш класс 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);
}

Ответ 4

Один из подходов, который я не видел, - это перегрузка конструктора, в результате которого распределение кучи использует последний аргумент как один (признанный, что вторая функция технически не является конструктором, она не возвращает экземпляр). Результатом будет что-то вроде (взято как основание вашего второго фрагмента кода):

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

Ответ 5

Может думать о распределителе 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 есть friend of point_t. Таким образом, он может получить доступ к своему конструктору private. Кроме того, вы можете добавить несколько конструкторов типа <A1, A2> внутри Allocator, чтобы сделать его более обобщенным. Преимущества:

  • Это не выглядит многословным.
  • Вам не нужно беспокоиться о оптимизации компилятора
  • Корабль friend не используется, поскольку Allocator является template и мы используем его исключительно для распределения кучи

Демо.