Именованный конструктор идиом и новый оператор
Я использую именованную конструкторскую идиому для создания объектов, потому что у меня много вызовов с одинаковыми параметрами, но объект должен быть создан по-разному.
С++ 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);
}
Есть ли более красивая версия, которая равна примерному коду?
Ответы
Ответ 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
и мы используем его исключительно для распределения кучи
Демо.