Я немного запутался. Если вы посмотрите на полезность (умных) указателей, вы получите разные мнения. Практически все согласны с тем, что следует использовать интеллектуальные указатели вместо обычных указателей, но существует также много мнений, что вам вообще не следует использовать указатели в современном C++.
Давайте рассмотрим эту абстрактную ситуацию (которая похожа на мою проблему): У вас есть один класс «Одежда», который имеет член типа «Шляпа».
class Hat {
public:
enum class HatType{
sombrero,
sun hat,
helmet,
beanie,
cowboy_hat
};
// some functions
private:
HatType type;
// some members
}
class Clothes {
public:
// some functions
private:
Hat currentHat;
// some other members of other types
}
Есть ли разница во времени выполнения, если я поменяю Hat на Hat* (или unique_ptr<Hat>?).
(Во многих функциях Clothes вам нужно будет вызывать что-то из Hat)
Я спрашиваю, потому что существует много разных типов головных уборов.
Например, сомбреро, шляпы от солнца, шлемы, шапочки и ковбойские шляпы. Прямо сейчас у моего класса шляпа есть перечислитель, в котором хранится тип шляпы.
Тип шляпы важен только для одной конкретной функции Hat, но это наиболее часто используемая функция.
Сейчас я использую простой switch case в этой конкретной функции, и в зависимости от типа шляпы функция оценивается немного по-другому. Это сработало нормально, но я думаю, что было бы разумнее просто создать собственный класс для каждого типа шляпы, который наследуется от одного основного класса Hat и переопределяет одну функцию.
Для этого, я думаю, мне пришлось бы заменить член currentHat в Clothes на любой тип указателя. Я исследовал, окажет ли это какое-либо негативное влияние на мою производительность (я подумал, может быть, потому, что расположение моего объекта currentHat и моего объекта Clothes в памяти может быть далеко друг от друга, но я понятия не имею, произойдет ли это, и будет ли это иметь какое-либо негативные эффекты с современными компиляторами).
Во время своего исследования я часто читал, что следует избегать указателей, это заставило меня задуматься ... некоторая информация, которую я нашел, также была очень старой, и я не знаю, устарела ли она. Есть ли лучший способ сделать это?
Есть ли у кого-нибудь опыт работы с такого рода проблемами? Было бы хорошо получить обратную связь, прежде чем я трачу много времени на изменение всего проекта ...
Примечание: я взял Clothes и Hats только в качестве примеров. В моем реальном приложении у меня есть разные типы, и я создаю много миллионов объектов из типа Clothes, и мне приходится вызывать функции Clothes, которые будут вызывать функции Hat много-много миллионов раз, поэтому меня беспокоит время выполнения.
Обновлено: Возможно, также стоит отметить, что до сих пор я полностью избегал указателей в своем приложении, в основном потому, что в некоторых книгах я читал, что следует избегать указателей :-)
EDIT2: Спасибо за все ваши ответы. Обратите внимание, что часть моего вопроса заключалась не в том, как это сделать (хотя ответы были очень полезны в этом случае), а скорее в том, пострадает ли от этого производительность.
Неправильная абстракция. Hat - это Clothes (э-э, это плохое название класса, потому что здесь нет единственного числа). Это должно быть смоделировано с использованием деривации. По вопросу об указателях: у них есть свое применение. Их можно использовать для описания того, что существует либо 0, либо 1 экземпляр. Их можно использовать для моделирования долевого владения. И, вероятно, ряд других вариантов использования.
В вашем случае std::variant может быть лучше.
Я не профессиональный программист. Я использую его, чтобы заниматься своей наукой. Поэтому я раньше не использовал полиморфизм ... но я думаю, что было бы неплохо начать использовать эти удобные инструменты
@KillzoneKid: Полиморфизм также доступен по ссылкам. У указателей нужно нет полиморфизма.
@KillzoneKid: Есть намного больше решений для полиморфного поведения. Один из них использует std :: variant. Вы также можете выполнить диспетчеризацию функций вручную в любом виде. Использование виртуальных методов / диспетчеризации vtable - одна из внутренних функций C++, но не единственная!
@IInspectable Очевидно, но не сделает ли использование указателей неудобным и сложным?
@Klaus Я все еще использую C++ 14. Перейду этот мост, когда доберусь туда;)
@KillzoneKid: Было бы так? Как именно? void foo(base const&) выглядит что неудобно? В какой-то степени дело вкуса. В любом случае это не соответствует вашему первоначальному утверждению. Что, по сути, неверно.
@IInspectable Не могу не подумать о непосредственных недостатках ссылок по сравнению с указателями и о том, как это может усложнить ситуацию.
@KillzoneKid: Что за "непосредственные недостатки"? Как это "усложнять вещи"? Простое высказывание мнения без объяснения причин или даже просто примера не очень помогает.
@KillzoneKid: С ++ 14 не нужен. Вы также можете использовать для этого ускорение.
Всегда нужно хотя бы немного подумать о правиле трех и пяти. Когда вы используете Hat *, вы думаете об этом как должен, копирование указателей не всегда хорошо заканчивается. Это не должно иметь большого значения, копирование чьей-то одежды часто не имеет особого смысла. Но сделайте это явным с помощью = delete, чтобы не беспокоиться об авариях.





Хорошо, давайте разберемся с фиаско указателя:
new / delete в C++На самом деле нет никаких исключений из этого правила. За исключением случаев, когда вы пишете библиотеку / фреймворк. Или какие-то причудливые вещи, требующие размещения нового. Но в коде пользователя new / delete должен на 100% отсутствовать.
Это подводит нас к следующему правилу:
Это часто встречается в сети как совет / правило «Не используйте необработанные указатели». Но, на мой взгляд, проблема не в необработанных указателях, а в необработанных указателях, которым принадлежит объект.
Для владения используйте умные указатели (unique_ptr, shared_ptr). Если указатель не является владельцем объекта, тогда ничего страшного, если вы используете необработанные указатели. Например, в древовидной структуре у вас может быть unique_ptr для дочерних элементов и необработанный указатель на родительский элемент.
Вы можете (возможно) также использовать указатель для обозначения значения по желанию через nullptr.
(Ну ... есть и другие способы, например стирание типа, но я не буду вдаваться в подробности в своем посте). Если вам нужен полиморфизм, вы не можете использовать значение. Если вы можете использовать ссылку, сделайте это, но в большинстве случаев вы не можете. Это часто означает, что вам нужно использовать unique_ptr.
Я ничего не могу с собой поделать, но если я вижу опечатку, я должен ее исправить;)
@ user463035818 это нормально. Я действительно благодарен
Я бы добавил, что необязательно указывать ... необязательное значение :)
Поскольку вам нужны головки разные, у вас есть два основных варианта:
1) Используя (умный) указатель, это хорошо известно и легко
2) Использование std :: variant
Это совершенно другой подход! Больше не нужен базовый класс и (чистые) виртуальные методы. Вместо этого используйте std::visit для доступа к текущему объекту в помеченный союз. Но за это приходится платить: диспетчеризация в std :: visit может быть дорогостоящей, если компилятор с трудом оптимизирует внутреннюю таблицу вызовов. Но если это возможно, он просто берет тег из варианта и использует его в качестве индекса для перегруженной функции из вашего вызова посещения, которая здесь дана как общая лямбда. Это не так быстро, как косвенный вызов через указатель vtable, но не больше, чем инструкции просмотра (если он оптимизирован!).
Как всегда: как ты хочешь пойти - дело вкуса.
Пример:
class Head1
{
public:
void Print() { std::cout << "Head1" << std::endl; }
};
class Head2
{
public:
void Print() { std::cout << "Head2" << std::endl; }
};
class Clothes
{
std::variant<Head1,Head2> currentHead;
public:
Clothes()
{
currentHead = Head1();
}
void Do() { std::visit( [](auto& head){ head.Print();}, currentHead); }
void SetHead( int id )
{
switch( id )
{
case 0:
currentHead= Head1();
break;
case 1:
currentHead= Head2();
break;
default:
std::cerr << "Wrong id for SetHead" << std::endl;
}
}
};
int main()
{
Clothes cl;
cl.Do();
cl.SetHead(1);
cl.Do();
cl.SetHead(0);
cl.Do();
}
Вот разбивка ваших трех предлагаемых реализаций и некоторых других:
Hat currentHat;Clothes является единственным владельцем Hatstd::unique_ptr<Hat> currentHat;Clothes по-прежнему однозначно владеет HatHatHat *currentHat;Clothes не владеет HatHat переживет Clothesstd::shared_ptr<Hat> currentHat;Clothes делит право собственности на Hat с ноль или более других Clothes.std::unique_ptr, в остальномHat ¤tHat;
Как вообще можно не использовать указатели? Это, вероятно, убило бы полиморфизм мертвым