Повторяющаяся константа-connundrum

Определенно в стиле С++. Дополнительный ввод поможет препятствовать тому, чтобы Вы бросили, когда Вы не были должны:-)

9
задан Mordachai 30 November 2009 в 21:49
поделиться

11 ответов

Я не верю, что это недостаток константной корректности как таковой, а скорее отсутствие удобная возможность обобщать метод по cv-квалификаторам (точно так же, как мы можем обобщать по типам с помощью шаблонов). Гипотетически, представьте, что вы могли бы написать что-то вроде:

template<cvqual CV>
inline CV ITEMIDLIST* GetNextItem(CV ITEMIDLIST * pidl)
{
    return pidl ? reinterpret_cast<CV ITEMIDLIST *>(reinterpret_cast<CV BYTE *>(pidl) + pidl->mkid.cb) : NULL;
}

ITEMIDLIST o;
const ITEMIDLIST co;


ITEMIDLIST* po = GetNextItem(&o); // CV is deduced to be nothing
ITEMIDLIST* pco = GetNextItem(&co); // CV is deduced to be "const"

Теперь вы действительно можете делать такие вещи с помощью метапрограммирования шаблонов, но это дает беспорядочно очень быстро:

template<class T, class TProto>
struct make_same_cv_as {
    typedef T result;
};

template<class T, class TProto>
struct make_same_cv_as<T, const TProto> {
    typedef const T result;
};

template<class T, class TProto>
struct make_same_cv_as<T, volatile TProto> {
    typedef volatile T result;
};

template<class T, class TProto>
struct make_same_cv_as<T, const volatile TProto> {
    typedef const volatile T result;
};

template<class CV_ITEMIDLIST>
inline CV_ITEMIDLIST* GetNextItem(CV_ITEMIDLIST* pidl)
{
    return pidl ? reinterpret_cast<CV_ITEMIDLIST*>(reinterpret_cast<typename make_same_cv_as<BYTE, CV_ITEMIDLIST>::result*>(pidl) + pidl->mkid.cb) : NULL;
}

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

В качестве альтернативы вы можете использовать шаблонную версию для повторного использования кода внутри вашего .cpp файла, а затем обернуть его в пара констант / неконстант и покажите это в заголовке. Таким образом, вы в значительной степени дублируете подпись функции.

3
ответ дан 4 December 2019 в 11:42
поделиться

Настоящая проблема здесь, похоже, в том, что вы предоставляете внешнему миру (относительно) прямой доступ к внутренним компонентам вашего класса. В некоторых случаях (например, классы-контейнеры) это может иметь смысл, но в большинстве случаев это означает, что вы предоставляете низкоуровневый доступ к внутренним компонентам в виде «глупых» данных, тогда как вы должны смотреть на операции более высокого уровня, которые клиентский код выполняет с этими данными, а затем предоставляет эти высокоуровневые операции непосредственно из вашего класса.

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

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

Edit2: Я также должен упомянуть, что C ++ 0x имеет новое определение auto Ключевое слово вместе с новым ключевым словом ( decltype ), которые значительно упрощают обработку значительного количества подобных вещей. Я не пробовал реализовать с ними эту точную функцию, но такой общий тип ситуации - это то, для чего они предназначены (например, автоматическое определение возвращаемого типа на основе переданных аргументов). Тем не менее, они обычно делают немного больше, чем вы хотите, поэтому они могут быть немного неуклюжими (если вообще полезны) для этой конкретной ситуации.

4
ответ дан 4 December 2019 в 11:42
поделиться

ИМО, это неудачный побочный продукт системы констант, но он возникает не так часто: только когда функции или методы выдают указатели / ссылки на что-то (независимо от того, изменить что-либо, функция не может выдать права, которых у нее нет, или корректность констант может серьезно нарушиться, поэтому эти перегрузки неизбежны).

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

namespace
{
    //here T is intended to be either [int] or [const int]
    //basically you can also assert at compile-time 
    //whether the type is what it is supposed to be
    template <class T>
    T* do_foo(T* p)
    {
        return p; //suppose this is something more complicated than that
    }
}

int* foo(int* p)
{
    return do_foo(p);
}

const int* foo(const int* p)
{
    return do_foo(p);
}

int main()
{
    int* p = 0;
    const int* q = foo(p);  //non-const version
    foo(q);  //const version
}
5
ответ дан 4 December 2019 в 11:42
поделиться

Ваши функции принимают указатель на pidl, который является либо константным, либо неконстантным. Либо ваша функция будет изменять параметр, либо нет - выберите один и закончите. Если функция также изменяет ваш объект, сделайте функцию неконстантной. Не понимаю, зачем вам в вашем случае нужны дублирующие функции.

2
ответ дан 4 December 2019 в 11:42
поделиться

Теперь у вас есть несколько обходных путей ...

Относительно лучших практик: укажите константную и неконстантную версии. Это проще всего поддерживать и использовать (IMO). Обеспечьте их на самом низком уровне, чтобы он мог размножаться наиболее легко. Не заставляйте клиентов приводить клиентов, вы бросаете им детали реализации, проблемы и недостатки. Они должны иметь возможность использовать ваши классы без хаков.

Я действительно не знаю идеального решения ... Я думаю, что ключевое слово в конечном итоге было бы самым простым (я отказываюсь использовать для этого макрос). Если мне нужны константные и неконстантные версии (что довольно часто), я просто определяю их дважды (как и вы) и не забываю держать их рядом друг с другом все время.

2
ответ дан 4 December 2019 в 11:42
поделиться

Я думаю, что это сложно обойтись, если вы посмотрите что-то вроде vector в STL, у вас будет то же самое:

iterator begin() {
    return (iterator(_Myfirst, this));
}
const_iterator begin() const {
    return (iterator(_Myfirst, this));
}

/ AB

1
ответ дан 4 December 2019 в 11:42
поделиться

В процессе работы я разработал решение, подобное тому, что предложил Павел Минаев . Однако я использую его немного по-другому, и я думаю, что это упрощает задачу.

Прежде всего, вам понадобятся две мета-функции: идентификация и добавление констант. Оба могут быть взяты из Boost , если вы его используете ( boost :: mpl :: identity из Boost.MPL и boost :: add_const ] из Boost.TypeTraits ). Однако они (особенно в этом ограниченном случае) настолько тривиальны, что их можно определить, не обращаясь к Boost .

РЕДАКТИРОВАТЬ: C ++ 0x предоставляет add_const type_traits header), так что это решение стало немного проще. Visual C ++ 2010 также предоставляет идентификатор (в заголовке утилиты ).


Определения следуют за

template<typename T>
struct identity
{
    typedef T type;
};

и

template<typename T>
struct add_const
{
    typedef const T type;
};

. Теперь, имея это, вы обычно предоставляете единственную реализацию функция-член как закрытая (или защищенная , если требуется) статическая функция, которая принимает this в качестве одного из параметров (в случае функции, не являющейся членом this , опущено).

Эта статическая функция также имеет параметр шаблона, являющийся метафункцией для работы с константой. Фактические функции будут вызывать эту функцию, указав в качестве аргумента шаблона либо identity (не const версия), либо add_const ( const версия) .

Обычно это будет выглядеть так:

class MyClass
{
public:
    Type1* fun(
        Type2& arg)
    {
        return fun_impl<identity>(this, arg);
    }

    const Type1* fun(
        const Type2& arg) const
    {
        return fun_impl<add_const>(this, arg);
    }

private:
    template<template<typename Type> class Constness>
    static typename Constness<Type1>::type* fun_impl(
        typename Constness<MyClass>::type* p_this,
        typename Constness<Type2>::type& arg)
    {
        // Do the implementation using Constness each time constness
        // of the type differs.
    }
};

Обратите внимание, что этот трюк не заставляет вас иметь реализацию в файле заголовка. Поскольку fun_impl является частным , его в любом случае нельзя использовать за пределами MyClass . Таким образом, вы можете переместить его определение в исходный файл (оставив объявление в классе, чтобы иметь доступ к внутренним компонентам класса), а также переместить определения fun в исходный файл.

Однако это лишь немного более подробное описание в случае более длинных нетривиальных функций это окупается


Я думаю, что это естественно. В конце концов, вы только что сказали, что вам нужно повторить один и тот же алгоритм (реализацию функции) для двух разных типов (один const и не const ). И для этого нужны шаблоны. Для написания алгоритмов, которые работают с любым типом и удовлетворяют некоторым базовым концепциям.

1
ответ дан 4 December 2019 в 11:42
поделиться

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

0
ответ дан 4 December 2019 в 11:42
поделиться

В вашем случае две версии не нужны. Неконстантная вещь неявно преобразуется в константную вещь, но не наоборот. Судя по названию вашей функции, похоже, что у GetNextItem не будет причин изменять pidl , поэтому вы можете переписать его следующим образом:

inline ITEMIDLIST * GetNextItem(const ITEMIDLIST * pidl);

Тогда клиенты могут вызывать его с константой или неконстантный ITEMIDLIST , и он будет просто работать:

ITEMIDLIST* item1;
const ITEMIDLIST* item2;

item1 = GetNextItem(item1);
item2 = GetNextItem(item2);
0
ответ дан 4 December 2019 в 11:42
поделиться

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

template<typename T>  // T should be a (possibly const) ITEMIDLIST *
inline T GetNextItem(T pidl)
{
    return pidl
        ? reinterpret_cast<T>(reinterpret_cast<const BYTE *>(pidl) + pidl->mkid.cb)
        : NULL;
}
0
ответ дан 4 December 2019 в 11:42
поделиться

Вы можете использовать шаблоны.

template<typename T, typename U>
inline T* GetNextItem(T* pidl)
{
    return pidl ? reinterpret_cast<T*>(reinterpret_cast<U*>(pidl) + pidl->mkid.cb) : NULL;
}

и использовать их как

ITEMDLIST* foo = GetNextItem<ITEMDLIST, BYTE>(bar);
const ITEMDLIST* constfoo = GetNextItem<const ITEMDLIST, const BYTE>(constbar);

или использовать некоторые определения типов, если вам надоело печатать.

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

Но я думаю, что в структуре ITEMDLIST может быть более глубокая проблема. Можно ли получить из ITEMDLIST? Чуть не забыл мой win32 раз ... плохие воспоминания ...

Edit: И вы, конечно, можете всегда злоупотребляйте препроцессором. Вот для чего это сделано. Поскольку вы уже используете win32, вы можете полностью перейти на темную сторону, больше не имеет значения; -)

0
ответ дан 4 December 2019 в 11:42
поделиться
Другие вопросы по тегам:

Похожие вопросы: