У меня есть класс, который пользователь использует для взаимодействия с системой. Этот класс использует 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;
}
То, что вы спрашиваете, невозможно. Эти две строки не могут вести себя по-разному:
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;
Если вы пытаетесь помешать другим людям делать неприятные вещи с вашим классом, вам нужно найти альтернативу использованию ссылки на локальную переменную. Если вы просто беспокоитесь о себе, то просто не делайте того, что не следует 🙂
Это точно такая же проблема, которая возникает из 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.
}
Модификаторы 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.
Возможным способом обойти это было бы наследование, но это, вероятно, станет еще более запутанным и проблематичным, не говоря уже о том, что снизит способность компиляторов оптимизировать.