Const объекты со ссылочной семантикой

У меня есть класс, который пользователь использует для взаимодействия с системой. Этот класс использует Pimpl для скрытия своих внутренних объектов, поэтому его единственный действительный член — это ссылка на реальный, скрытый объект, который выполняет всю работу.

Поскольку класс имеет семантику ссылок, он обычно передается по значению, как указатель. Это приводит к проблеме с const правильность. Вы можете сломать const природа класса очень легко, просто скопировав const значение в неconst значение. И нет способа избежать этого, кроме как полностью предотвратить копирование.

Я хочу иметь возможность вернуться const значения этих, что сохраняет const характер объекта. Без создание нового класса или что-то.

В основном я хочу, чтобы это не сработало:

struct Ref
{
int &t;
Ref(int &_t) : t(_t) {}
};

Ref MakeRef(int &t) { return Ref(t); }

int main()
{
int foo = 5;
const Ref r(foo);
const Ref c(r);            //This should be allowed.
Ref other = MakeRef(foo);  //This should also be allowed.
Ref bar(r);                //This should fail to compile somehow.

return 0;
}

В конце концов, это не сработало бы, если бы я сделал это напрямую:

int &MakeRef(int &t) {return t;}

int main()
{
int foo = 5;
const int &r(foo);
const int &c(r);            //This compiles.
int &other = MakeRef(foo);  //This compiles.
int &bar(r);                //This fails to compile.

return 0;
}

0

Решение

То, что вы спрашиваете, невозможно. Эти две строки не могут вести себя по-разному:

const Ref c(r);            //This should be allowed.
Ref bar(r);                //This should fail to compile somehow.

Обе строки будут выполняться через один и тот же путь кода, обе они будут выполняться через один и тот же конструктор копирования (ваш или автоматически сгенерированный). Разница лишь в том, что первое приведет к окончательной переменной const.

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

const Ref c(r);
Ref &bar = (Ref&)c;

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

0

Другие решения

Это точно такая же проблема, которая возникает из const T* а также T* const: изменчивость ссылки и референта различна. Существуют допустимые варианты использования в C ++ для всех четырех возможных комбинаций. Я бы сделал разные типы для «ссылки на T» и «ссылки на const T»:

#include <type_traits>

template <typename T>
struct Ref
{
T &t;
Ref(T &_t) : t(_t) {}
Ref(const Ref<typename std::remove_cv<T>::type>& other) : t(other.t) {}
};

template <typename T>
Ref<T> MakeRef(T& t) { return {t}; }

template <typename T>
Ref<const T> MakeConstRef(const T& t) { return {t}; }

int main()
{
int foo = 5;
auto r = MakeConstRef(foo);
auto c = r;                 // This is allowed.
auto other = MakeRef(foo);  // This is also allowed.
Ref<const int> baz = other; // This works, too.
Ref<int> bar = c;           // This fails to compile, as desired.
}

Живой пример на Ideone.

2

Модификаторы CV в общем случае не перемещаются мимо ссылки или указателя из определения класса / структуры. Это потому, что это не агрегатная часть объекта, поэтому технически вы на самом деле не действуете на объект, а просто на что-то, на что он указывает.

То, что вам нужно сделать, это свернуть свою собственную константу, как это:

struct constRef
{
int const& _t;
constRef(int const& rxo) : _t(rxo) {}
constRef(constRef const& rxo) : _t(rxo._t) {}

int const & t() const { return _t; }
static constRef Make(int const & t) { return t; }
};

struct Ref
{
int& _t;
Ref(int& ro) : _t(ro) {}
Ref(Ref const& rxo) : _t(rxo._t) {}
operator constRef() const { return constRef(_t); }

int& t() { return _t; }
static Ref Make(int& t) { return t; }
};int main()
{
int foo = 5;
Ref foo2(foo);
constRef r(foo2);            // non-const -> const         This compiles.
constRef c(r);               // const -> const             This compiles.
Ref other = Ref::Make(foo);  // non-const -> non-const     This compiles
Ref bar(r);                  // const -> non-const         This fails to compile

return 0;
}

Это обеспечивает автоматическое одностороннее преобразование между неконстантным и константным типом через Ref::operator constRef() const функция преобразования. Работающую модель можно найти Вот.

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

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

0
По вопросам рекламы [email protected]