использование *void в качестве буфера для static_cast

Итак, я иду так:

class A;

class B : public A;

class C : public B;

vector<A*> *vecA;

vector<C*> *vecC;

И я хочу превратить vectC в vecA.

vector<A*> *_newA = static_cast< vector<A*>* >(vecC); //gives an error

Поэтому я использовал указатель void в качестве буфера и приведения:

void *buffer = vecC;

vector<A*> *_newA = static_cast< vector<A*>* >(buffer); //works

Это действительно? Есть ли другой способ сделать это?


person nahpr    schedule 06.08.2012    source источник
comment
Указатель на std::vector, это просто неправильно! Не делай этого!   -  person Tony The Lion    schedule 06.08.2012
comment
Кроме того, вы должны отбрасывать то, что вы помещаете в вектор, а не сам вектор.   -  person Tony The Lion    schedule 06.08.2012
comment
И почти нет причин для динамического выделения вектора.   -  person Torsten Robitzki    schedule 06.08.2012
comment
Я предлагаю вам достать хорошую книгу по C++.   -  person Etienne de Martel    schedule 06.08.2012
comment
new — это ключевое слово в C++. Это не разрешено в качестве идентификатора.   -  person pmr    schedule 06.08.2012
comment
new в качестве имени переменной является опечаткой. ¯_(ツ)_/¯   -  person nahpr    schedule 06.08.2012


Ответы (5)


У вас должен быть просто std::vector<A*>, а затем, когда вы захотите поместить внутрь B или C, вы можете сделать это:

 std::vector<A*> vec;
 vec.push_back(b);

Это будет работать намного лучше, чем все, что вы делаете сейчас.

Кроме того, вы должны использовать std::unique_ptr или std::shared_ptr, в зависимости от семантики владения вашими указателями. Владение необработанными указателями - нет-нет!

person Tony The Lion    schedule 06.08.2012
comment
разве vec.push_back(b) не подойдет? - person juanchopanza; 06.08.2012
comment
Это было бы - компилятор сгенерирует неявное приведение от производного класса к его базовому классу. Хотя static_cast<A*> не помешает. - person marko; 06.08.2012
comment
@Marko Если b является B *, то не нужно ничего разыгрывать. - person Etienne de Martel; 06.08.2012
comment
Я хочу проголосовать за этот ответ, но последнее предложение настолько широкое, что это уже не хороший совет. - person Ben Voigt; 06.08.2012
comment
@BenVoigt Я изменил последнее предложение. - person Tony The Lion; 07.08.2012

Это действительно?

Нет, это не так. Совершенно неизвестно, что произойдет, если вы получите к нему доступ.

Есть ли другой способ сделать это?

да. Это зависит от того, что вы хотите сделать:

  1. скопируйте содержимое: std::copy(vecC.begin(), vecC.end(), std::back_inserter(vecA));

  2. создайте адаптер, который ведет себя как контейнер с произвольным доступом A*, если задан контейнер или итератор C* или любого другого производного типа

  3. Я уверен, что есть и другие решения, просто скажите нам, что вы хотите сделать

person Paul Michalik    schedule 06.08.2012
comment
совершенно неопределенное есть только в формальном. на практике нет ни особой специализации vector, которая могла бы нанести ущерб, ни разницы в смещениях значений указателя, ни неполиморфности базового класса, ни виртуализации, представленной в производном классе (что может привести к тому, что базовый и производный указатели будут один и тот же объект имеет разные битовые паттерны). Короче говоря, нужно быть очень осторожным. но на практике при такой осторожности преобразование работает. тем не менее, это позволяет нехорошие вещи. см. мой ответ для этого. - person Cheers and hth. - Alf; 06.08.2012
comment
@Alf: На практике это нарушение строгого сглаживания и может вызвать всевозможные сбои после оптимизации. - person Ben Voigt; 06.08.2012
comment
@Alf: «undefined» означает, что стандарт не говорит, что произойдет, если перечисленные вами точки недействительны, чего он не гарантирует ... Как вы правильно утверждаете, это может даже работать, но, строго говоря, это также подпадает под действие 'undefined' :-) В любом случае это вредит наложенному принципу, который может вызвать любые другие плохие вещи... - person Paul Michalik; 06.08.2012

Волшебные слова, которые вам, вероятно, нужно знать, это «ковариантность» и «контравариантность». Посмотрите здесь по очень близкой теме.

Краткий ответ: нет аккуратного, разумного способа сделать преобразование, которое вы хотите сделать, просто с помощью приведения. Вам нужно будет скопировать содержимое вашего старого вектора в новый вектор.

Но что еще более важно: 1. Никогда не используйте new для стандартного типа контейнера. 2. Не передавайте указатели на стандартные типы контейнеров. 3. Не передавайте необработанные указатели на свои собственные объекты, вместо этого используйте std::shared_ptr.

person Rook    schedule 06.08.2012
comment
Все три ваши рекомендации на прощание слишком упрощены и неверны, как написано. - person Ben Voigt; 06.08.2012
comment
@BenVoigt: это поразительно бесполезный отзыв. Отличная работа. - person Rook; 06.08.2012
comment
@nahpr: Да, передача по ссылке вполне разумна. - person Rook; 06.08.2012
comment
@Rook: Извините, я посмотрел только на ваш псевдоним, а не на вашу репутацию, подумал, что вы более опытный пользователь. В любом случае: (1) Использование new в стандартном типе контейнера, а затем вставка указателя внутрь интеллектуального указателя RAII вполне разумно. (2) Если передача по ссылке является разумной, то передача по указателю является разумной. Мы не говорим о продлении срока службы временных (которые доступны только для справки). (3) Необработанные указатели являются типом итератора для некоторых контейнеров, и имеет смысл использовать их в качестве итераторов (т.е. отсутствие владения и более короткое время жизни, чем у контейнера). - person Ben Voigt; 06.08.2012
comment
И это только несколько примеров, когда ваше руководство не работает, их гораздо больше. - person Ben Voigt; 06.08.2012
comment
Вот еще одно хорошее изложение принципа, что интеллектуальные указатели не являются лучшими во всех обстоятельствах: stackoverflow.com/a/7658089/103167 - person Ben Voigt; 06.08.2012
comment
@BenVoigt: спасибо. Я больше всего хотел, чтобы вы указали, где я был неправ, в интересах ОП! В любом случае, можете ли вы привести полезный пример, когда вы можете захотеть вставить стандартный контейнер в интеллектуальный указатель? Я думаю, было бы полезно обернуть один в unique_ptr. Хотя (2) является стилистическим выбором, я не вижу веских причин использовать указатели на контейнеры вместо ссылок в общем случае; например, общедоступный API стандартных контейнеров не использует указатели, IIRC. (3) не совсем так относится к коду OP, но вы правы в том, что ссылки/указатели, не принадлежащие владельцам, часто имеют смысл. - person Rook; 06.08.2012
comment
@Rook: Возможно, вы пишете функцию для отслеживания маршрута до определенного хоста в Интернете. Вы можете вернуть расстояние в прыжках и иметь параметр типа std::vector<struct hostent>*. Если указан вектор, он заполняется этой информацией. Но клиент может получить только количество переходов, передав NULL, и в этом случае функция экономит время, не просматривая имена хостов. - person Ben Voigt; 06.08.2012
comment
Это было упомянуто совсем недавно в другом вопросе C++. Я сам предпочитал вариант перегрузки функций. - person Rook; 06.08.2012
comment
@Rook: Тогда вы застряли с дублированием кода. Необязательный аргумент со значением по умолчанию 0 — это простой способ предоставить обе подписи без дублирования. И да, вы можете скрыть это за фасадом перегруженных общедоступных функций, но это будет добавление лишнего кода без всякой цели. - person Ben Voigt; 07.08.2012

Вместо обхода через void* в C++11 просто используйте reinterpret_cast.

Однако желаемое преобразование разрешает очень плохие вещи:

#include <iostream>
#include <vector>
using namespace std;

class Base {};

class Derived: public Base
{ public: int x; Derived(): x(42) {} };

int main()
{
    vector< Derived* >  v;
    Derived             d;

    v.push_back( &d );
    cout << v.back()->x << endl;        // 42

    vector< Base* >*    pbv =
        reinterpret_cast< vector< Base* >* >( &v );
    Base                b;

    pbv->push_back( &b );
    cout << v.back()->x << endl;        // indeterminate value
}

Это примерно та же проблема, что и у вас с преобразованием T** в T const** (не разрешено, может допустить нехорошие вещи). Суть в том, что не делайте этого, если вы точно не знаете, что делаете. Чем дольше это, хм, ну, здесь нет места для обсуждения, но это включает в себя различие между изменяемыми и неизменяемыми коллекциями и очень и очень осторожное.


edit: как уже заявили другие (кто не задавал вопрос о преобразовании), практическим решением является вектор A указателей или интеллектуальных указателей, а затем использование полиморфизма С++ (т. е. виртуальных функций-членов).

person Cheers and hth. - Alf    schedule 06.08.2012
comment
-1 за намек на то, что плохие вещи могут происходить только при записи в контейнер. - person Ben Voigt; 06.08.2012
comment
@Ben: может быть, ты думаешь о строгих правилах псевдонимов. это неработающая опция компилятора для конкретного компилятора (а именно для gcc), а не неработающий язык. обратите внимание, что список поддерживаемых преобразований, который иногда цитируется в поддержку поведения g++, никогда не включал преобразование из первого элемента структуры в саму структуру и наоборот, что явно поддерживается в другом месте стандарта. то есть этот список никогда не был полным, каким он должен был быть, чтобы быть поддержкой, на которую иногда претендуют. - person Cheers and hth. - Alf; 07.08.2012
comment
@Alf: любое разыменование указателя, созданного с помощью reinterpret_cast, является неопределенным поведением. Строгое использование псевдонимов — это один из способов, с помощью которого ваш код может сломаться, даже если расположение памяти совпадает. И нет, строгие псевдонимы являются частью языка. Это не сломанный язык, но он делает ваш предложенный код неработающим. Поскольку существует много неработающего кода, gcc предоставляет возможность отключить оптимизацию, связанную с анализом алиасинга, но строгое правило алиасинга не является специфичным для gcc. - person Ben Voigt; 07.08.2012
comment
@Ben: вы правы, что reinterpret_cast - это формально неопределенное поведение. но это на языке, который будет использоваться: системное программное обеспечение на всех трех основных настольных платформах зависит от компилятора, определяющего это поведение. и нет, строгие псевдонимы не являются частью языка и не упоминаются в стандарте. прочитайте еще раз, что я написал (список неполный). я не стал добавлять более веских аргументов, потому что нет места для обсуждения, и формальной неполноты списка достаточно само по себе, но будьте уверены, что это полностью проблема опции gcc, как и оптимизация с плавающей запятой в этом компиляторе. - person Cheers and hth. - Alf; 07.08.2012

Мне кажется, что вы ищете поддержку covariant для общих (шаблонных ) типы. Это вообще не поддерживается C++. Он поддерживается CLR и C#/VB, но только в последние версии.

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

person aSteve    schedule 06.08.2012