#c #constructor
#c #конструктор
Вопрос:
Рассмотрим гипотетический сценарий, в котором два класса могут быть сконструированы по умолчанию или друг из друга, но любой способ считается дорогостоящим (далее следует намеренно надуманный пример):
struct PrivateKey;
struct PublicKey {
PublicKey(); // generate a random public key (1 minute)
PublicKey(const PrivateKeyamp; b); // find a public key corresponding to a private key (1 year)
...members...
};
struct PrivateKey {
PrivateKey(); // generate a random private key (1 minute)
PrivateKey(const PublicKeyamp; a); // find a private key corresponding to a public key (1 year)
...members...
};
(Это, конечно, можно было бы свести к одному классу, но на обоснованность вопроса это не влияет. Допустим, ради согласованности нет симметрии между одним и другим.)
Теперь есть структура, которая содержит экземпляры обоих и нуждается в этой перекрестной инициализации. Однако нам могут понадобиться оба направления, поэтому списки инициализаторов на самом деле не могут его сократить (они выполняются не в указанном порядке, а в порядке определения элементов, и здесь порядок не может быть исправлен):
struct X {
PublicKey a;
PrivateKey b;
X(int): a(), b(a) { }
X(float): b(), a(b) { } // UB: a(b) happens before b is initialized
};
Конечно, я мог бы попробовать:
struct X {
PublicKey a;
PrivateKey b;
X(int): a(), b(a) { }
X(float): a(), b() { a = PublicKey(b); }
};
но у этого есть несколько проблем, из которых запуск дорогостоящей конструкции по умолчанию PublicKey
во втором конструкторе X
, чтобы сразу же выдать результат, является только первой. Могут быть побочные эффекты для PublicKey::PublicKey()
. И то, и другое все еще можно было бы смягчить, создав дешевый частный конструктор, доступный только другу, X
который поместил бы класс в некоторое фиктивное состояние, но добавил бы некоторые ссылочные или постоянные члены, и класс не может быть переназначаемым или заменяемым, запрещая любые изменения в теле X::X(float)
. Есть ли лучший шаблон для подражания?
Комментарии:
1. Используйте
PublicKey *a
иPrivateKey *b
и создавайте их внутри тела конструктора{ ... }
.2. @AJNeufeld Поле для ответа находится внизу.
3. Кристально чистый. Боже, о чем я вообще думал? Хотите опубликовать ответ?
4. Кстати, потеря 1 минуты за 1 год на самом деле не проблема.
5. @Jarod42 понял это после публикации. Но я не хотел думать о другой проблеме, которая обладает аналогичными свойствами.
Ответ №1:
Проблемы с порядком построения можно избежать, используя указатели на содержащиеся классы вместо непосредственного встраивания содержащихся классов и самостоятельного построения содержащихся объектов внутри тела конструктора.
struct X {
std::unique_ptr<PublicKey> a;
std::unique_ptr<PrivateKey> b;
X(int) {
a = std::make_unique<PublicKey>();
b = std::make_unique<PrivateKey>(*a);
}
X(float) {
b = std::make_unique<PrivateKey>();
a = std::make_unique<PublicKey>(*b);
}
};
Комментарии:
1. Возможно, даже если код не требует пояснений, в коде должно быть объяснение, потому что это может помочь тем, кто в будущем.
2. Вы могли бы упомянуть, что на практике
std::unique_ptr
следует использовать вместо необработанных указателей.3. Теперь, когда вы используете,
unique_ptr
вы должны использоватьmake_unique
в телах конструктора.4. @NathanOliver Из-за множества предложенных улучшений я изменил это на вики сообщества. Дополнительные улучшения могут быть внесены непосредственно в ответ.
5. Сделал некоторые сам, чтобы спасти работу других. Большое спасибо!
Ответ №2:
Если классы, по крайней мере, могут быть сконструированы для перемещения, вы должны быть в состоянии сделать это:
struct KeyPair
{
PublicKey a;
PrivateKey b;
KeyPair(std::pair<PublicKey, PrivateKey> amp;amp;data) :
a(std::move(data.first)),
b(std::move(data.second))
{}
};
std::pair<PublicKey, PrivateKey> computePublicFirst()
{
PublicKey a;
PrivateKey b(a);
return {std::move(a), std::move(b)};
}
std::pair<PublicKey, PrivateKey> computePrivateFirst()
{
PrivateKey b;
PublicKey a(b);
return {std::move(a), std::move(b)};
}
struct X
{
KeyPair keys;
X(int) : keys(computePublicFirst()) {}
X(float) : keys(computePrivateFirst()) {}
};
Никаких назначений перемещения не происходит, только перемещение конструкции.
Комментарии:
1. Я хотел бы принять оба решения. Я выбрал другой ответ за его элегантность и семантическую ясность, но ваш идеально подходит, если вы хотите устранить дополнительное разрешение указателя при каждом вызове метода A или B, что я часто считаю важным.