Псевдоним строгого указателя: любое решение для конкретной проблемы?

У меня проблема, вызванная нарушением строгого правила псевдонима указателя. У меня есть тип T, полученный из шаблона, и некоторый интегральный тип Int того же размера (как и sizeof). Мой код по существу делает следующее:

T x = some_other_t;
if (*reinterpret_cast <Int*> (&x) == 0)
  ...

Поскольку T — это некоторый произвольный (кроме ограничения по размеру) тип, который может иметь конструктор, я не могу создать объединение T и Int. (Это разрешено только в C++0x и даже не поддерживается GCC).

Можно ли как-нибудь переписать приведенный выше псевдокод, чтобы сохранить функциональность и избежать нарушения строгого правила псевдонимов? Обратите внимание, что это шаблон, я не могу контролировать T или значение some_other_t; присваивание и последующее сравнение происходят внутри шаблонного кода.

(Кстати, приведенный выше код начал ломаться в GCC 4.5, если T содержит какие-либо битовые поля.)


person doublep    schedule 05.06.2010    source источник
comment
Что ты пытаешься сделать? Я не могу вспомнить многих ситуаций, когда этот код имеет смысл. Это, конечно, не очень хорошо указано в стандарте. Итак, если предположить, что этот хак действительно необходим (что, вероятно, не так), вам, возможно, придется просто использовать соответствующий флаг компилятора, чтобы отключить строгое сглаживание.   -  person jalf    schedule 05.06.2010
comment
@jalf: это уникальный контейнер. Я помечаю пустые позиции целым 0. Однако, поскольку T может быть любым, включая побитовый 0, мне нужно пометить не более одной позиции как непустую, даже если она выглядит пустой. Сравнение является проверкой того, следует ли x помечать так или нет.   -  person doublep    schedule 05.06.2010
comment
Я не понимаю, как вы решаете эту проблему - как reinterpret_cast позволяет вам игнорировать две разные причины для хранения 0?   -  person Stephen    schedule 05.06.2010
comment
@doublep: Это все еще не совсем ясно. По сути, вы пытаетесь проверить, представлено ли x в памяти всеми нулями?   -  person Oliver Charlesworth    schedule 05.06.2010
comment
@Stephen: Если два T не равны ==, они не могут быть равны по битам, по крайней мере, для любого полезного определения равенства в контейнере. Таким образом, в уникальном контейнере не может быть двух нулевых побитовых элементов (если справедливо равенство).   -  person doublep    schedule 05.06.2010


Ответы (6)


static inline int is_T_0(const T *ob)
{
        int p;
        memcpy(&p, ob, sizeof(int));
        return p == 0;
}

void myfunc(void)
{
    T x = some_other_t;
    if (is_T_0(&x))
        ...

В моей системе GCC оптимизирует как is_T_0(), так и memcpy(), в результате чего в myfunc() всего несколько инструкций по сборке.

person Daniel Stutzbach    schedule 05.06.2010
comment
Согласно другой вопрос, это путь. - person doublep; 06.06.2010
comment
Лично я бы сделал использование чище (для меня), заставив is_T_0 взять const T&, но вы должны убедиться, что GCC по-прежнему оптимизирует все в этом случае. - person Chris Lutz; 06.06.2010

Вы слышали о boost::optional ?

Я должен признать, что мне неясна реальная проблема здесь... но boost::Optional позволяет сохранять по значению и при этом знать, была ли инициализирована фактическая память. Я также допускаю строительство и разрушение на месте, так что, думаю, это может подойти.

ИЗМЕНИТЬ:

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

К сожалению, у вашего решения есть огромная проблема: оно неверно. Если когда-нибудь T можно каким-то образом представить null битовым шаблоном, то вы подумаете, что это унитаризованная память.

Вам придется прибегнуть к помощи, чтобы добавить хотя бы один бит информации. На самом деле это немного, ведь это всего 3% прироста (33 бита на 4 байта).

Вы могли бы, например, использовать некоторую имитацию boost::optional, но в виде массива (чтобы избежать потери заполнения).

template <class T, size_t N>
class OptionalArray
{
public:


private:
  typedef unsigned char byte;

  byte mIndex[N/8+1];
  byte mData[sizeof(T)*N]; // note: alignment not considered
};

Тогда это так просто:

template <class T, size_t N>
bool OptionalArray<T,N>::null(size_t const i) const
{
  return mIndex[i/8] & (1 << (i%8));
}

template <class T, size_t N>
T& OptionalArray<T,N>::operator[](size_t const i)
{
  assert(!this->null(i));
  return *reinterpret_cast<T*>(mData[sizeof(T)*i]);
}

примечание. Для простоты я не рассматривал вопрос выравнивания. Если не разбираетесь в теме, прочтите о ней, прежде чем возиться с памятью :)

person Matthieu M.    schedule 05.06.2010
comment
При текущей настройке я использую 4 байта на элемент, т.е. не больше размера элемента. Речь идет об эффективности: это важно, когда у вас много тысяч элементов. - person doublep; 05.06.2010
comment
К сожалению, у вашего решения есть огромная проблема: оно неверно — можете ли вы рассказать об этом подробнее? - person doublep; 05.06.2010
comment
Прочитайте следующее предложение: скажем, T является Int, и я присваиваю ему 0, тогда ваш тест скажет: unialized, пока он инициализирован (до 0). Вы в основном используете магическое значение, но без какой-либо гарантии, что ваше магическое значение выходит за рамки значимых значений. Вот почему вам нужен как минимум еще один бит для хранения информации. - person Matthieu M.; 05.06.2010
comment
Как насчет того, чтобы не делать предположений, особенно когда это не нужно для ответа на вопрос? У меня определенно есть этот случай. Это даже где-то в комментариях к вопросам, хотя это не очень относится к исходной проблеме. - person doublep; 05.06.2010
comment
Кстати, по иронии судьбы, boost::optional также генерирует предупреждения о строгом сглаживании. - person Emile Cormier; 05.06.2010

Как насчет этого:

Int zero = 0;
T x = some_other_t;
if (std::memcmp(&x, &zero, sizeof(zero)) == 0)

Это может быть не так эффективно, но должно избавиться от предупреждения.


ПРИЛОЖЕНИЕ №1:

Поскольку T ограничено тем же размером, что и Int, создайте себе фиктивное побитовое нулевое значение типа T и сравните непосредственно с ним (вместо приведения и сравнения с Int(0)).

Если ваша программа однопоточная, у вас может быть что-то вроде этого:

template <typename T>
class Container
{
public:
    void foo(T val)
    {
        if (zero_ == val)
        {
            // Do something
        }
    }

private:
    struct Zero
    {
        Zero() {memset(&val, 0, sizeof(val));}
        bool operator==(const T& rhs) const {return val == rhs;}
        T val;
    };
    static Zero zero_;
};

Если он многопоточный, вам следует избегать использования статического члена zero_ и иметь каждый экземпляр контейнера, содержащий свой собственный член zero_:

template <typename T>
class MTContainer
{
public:
    MTContainer() {memset(zero_, 0, sizeof(zero_));}

    void foo(T val)
    {
        if (val == zero_)
        {
            // Do something
        }
    }

private:
    T zero_;
};

ПРИЛОЖЕНИЕ №2:

Позвольте мне изложить вышеприведенное дополнение по-другому, проще:

// zero is a member variable and is inialized in the container's constructor
T zero;
std::memset(&zero, 0, sizeof(zero));

T x = some_other_t;
if (x == zero)
person Emile Cormier    schedule 05.06.2010
comment
Проголосовал, потому что это, кажется, работает нормально. Однако я бы хотел лучшего решения... - person doublep; 05.06.2010
comment
@doublep: к моему ответу добавлено еще одно, более эффективное решение. - person Emile Cormier; 05.06.2010
comment
К сожалению, это ужасно для производительности. Когда T сам равен int, я получаю 2,5-кратное замедление на GCC 4.5 -O2 (это составной тест почти реального использования). Судя по всему, GCC не может оптимизировать memcmp(). - person doublep; 05.06.2010
comment
@douplep: я вообще не использую memcmp() в своем обновленном ответе. - person Emile Cormier; 05.06.2010
comment
Это предполагает, что T является конструктором по умолчанию, что может быть не так. Это также предполагает, что 0x00000000 не является осмысленным значением T. - person Matthieu M.; 05.06.2010
comment
@doublep: добавил еще одно дополнение к моему ответу. - person Emile Cormier; 05.06.2010
comment
@ Эмиль Кормье: Насколько я вижу, в вашем обновленном ответе используется T::operator==(). Это нет-нет, потому что вы не знаете, что это делает. Например, T может быть классом-оболочкой для некоторого указателя и в == утверждать, что он не равен нулю. Или разыменуйте указатель (побитовый 0) и, таким образом, вызовите ошибку сегментации. - person doublep; 05.06.2010
comment
@Matthieu: Разве не требуется, чтобы T был конструируемым по умолчанию для контейнеров STL? В одном из комментариев duplep говорит, что хочет сравнить T с побитовыми нулями. В моем решении я не предполагаю, что T можно инициализировать с помощью 0 (это может быть кортеж), поэтому я задаю биты zero_ равными 0x00000000, используя memset. - person Emile Cormier; 05.06.2010
comment
@Эмиль Кормье: Да, T конструируется по умолчанию. Но вы не можете вызывать T::operator== для унифицированного объекта. Для распространенных типов это будет работать, но, вообще говоря, это поведение undefined. Легко создать тип, который потерпит неудачу в таких обстоятельствах. - person doublep; 05.06.2010
comment
@doublep: Хорошее замечание о моем решении, заставляющем T иметь operator==. - person Emile Cormier; 05.06.2010

Почему бы не просто:

const Int zero = 0;
if (memcmp(&some_other_t, &zero, sizeof(zero)) == 0)
  /* some_other_t is 0 */

(вы можете попробовать добавить квалификатор static к zero, чтобы увидеть, влияет ли это на производительность)

person CAFxX    schedule 15.07.2012

Используйте 33-битный компьютер. ;-П

person Emile Cormier    schedule 05.06.2010

Похоже на хак, но, видимо, я нашел решение: использовать volatile для приведения Int. По сути, то, что я делаю сейчас, это:

T x = some_other_t;
if (*reinterpret_cast <volatile Int*> (&x) == 0)
  ...

Проблема с битовым полем T ушла. Тем не менее, я не очень доволен этим, поскольку volatile не очень хорошо определен в С++ AFAIK...

person doublep    schedule 05.06.2010
comment
Вы можете быть на что-то. Я надеюсь, что языковые юристы увидят это и прокомментируют. - person Emile Cormier; 05.06.2010
comment
Я не эксперт в этих вопросах, но мое понимание ключевого слова volatile заключается в том, что его единственный эффект заключается в том, чтобы гарантировать, что компилятор не будет выполнять никаких оптимизаций, которые могут привести к неправильному поведению, если переменная ссылается, например, на. регистр ввода-вывода, то есть что-то, что может быть изменено извне. То, что у вас есть выше, не является одной из этих ситуаций, поэтому вы, вероятно, не можете полагаться на volatile, чтобы гарантировать правильное поведение. Другими словами, это не обходной путь для строгих правил псевдонимов. - person Oliver Charlesworth; 05.06.2010
comment
@Oli Charlesworth: Из спецификации: [Примечание: volatile - это подсказка реализации, позволяющая избежать агрессивной оптимизации с участием объекта, поскольку значение объекта может быть изменено средствами, не обнаруживаемыми реализацией. Подробную семантику см. в 1.9. В общем, семантика volatile должна быть такой же в C++, как и в C. — примечание в конце] Я бы сказал, что этот случай может быть изменен средствами, не обнаруживаемыми определением реализации, хотя это, конечно, спорно. - person doublep; 05.06.2010
comment
Я не языковой юрист, поэтому семантика этой спецификации действительно подлежит обсуждению. Однако, если бы для решения проблем со строгим псевдонимом требовалось только использование volatile, больше не было бы вопросов о строгом псевдониме в Stack Overflow! То, что таких вопросов по-прежнему бесчисленное множество, означает, что это не решение! - person Oliver Charlesworth; 05.06.2010
comment
@Oli Charlesworth: Вы правы; Я задал этот как отдельный вопрос < /а>. - person doublep; 05.06.2010
comment
@doublep Я бы сказал, что этот случай может быть изменен средствами, не обнаруживаемыми определением реализации, хотя это, конечно, спорно. Нет, это не так: если вы хотите рассматривать переменную как I /O зарегистрируйтесь, а затем сделайте все доступы изменчивыми: это решит проблему алиасинга. Если сделать это конкретное чтение энергозависимым, это не решит проблему псевдонимов по сравнению с другими энергонезависимыми применениями. - person curiousguy; 19.12.2011