Дифференцирование между константными ссылками на неизменяемые и изменяемые объекты
Есть ли приемлемый способ в С++ для дифференциации ссылок на константные ссылки на неизменяемые объекты по сравнению с изменчивыми?
например.
class DataBuffer {
// ...
};
class Params {
// ...
};
class C {
public:
// Given references must be valid during instance lifetime.
C(const Params& immutableParameters, const DataBuffer& mutableDataBuffer) :
m_immutableParameters{immutableParameters},
m_mutableDataBuffer{mutableDataBuffer}
{
}
void processBuffer();
private:
const Params& m_immutableParameters;
const DataBuffer& m_mutableDataBuffer;
};
Здесь семантическая разница дается именно в именах.
Проблема в том, что переменные экземпляра const&
позволяют вам знать, что объект не будет изменен экземпляром. В интерфейсе нет различий, могут ли они быть изменены в другом месте, что, по моему мнению, является полезной функцией, которую можно описать в интерфейсе.
Выражение этого с помощью системы типов поможет сделать интерфейсы более понятными, позволяя компилятору поймать ошибки (например, случайное изменение параметров, переданных экземпляру C
, вне экземпляра, в примере выше) и, возможно, помощь с оптимизация компилятора.
Предполагая, что ответ заключается в том, что различие невозможно в С++, может быть, есть что-то близкое, которое может быть достигнуто с помощью некоторых масок шаблонов?
Ответы
Ответ 1
Неизменность не является частью системы типа С++. Таким образом, вы не можете различать неизменяемые объекты и изменчивые. И даже если бы вы могли, std::as_const
всегда испортит вашу попытку сделать это.
Если вы пишете интерфейс, требующий неизменности объектов, самый простой способ справиться с этим - обратиться к фундаментальной теореме разработки программного обеспечения: "Мы можем решить любую проблему, введя дополнительный уровень косвенности". Поэтому сделайте неизменную часть системы типов. Например (FYI: использует некоторые небольшие библиотеки С++ 17):
template<typename T>
class immutable
{
public:
template<typename ...Args>
immutable(std::in_place_t, Args &&...args) t(std::forward<Args>(args)...) {}
immutable() = default;
~immutable() = default;
immutable(const immutable &) = default;
//Not moveable.
immutable(immutable &&) = delete;
//Not assignable.
immutable operator=(const immutable &) = delete;
immutable operator=(immutable &&) = delete;
const T* operator->() const {return &t;}
const T& operator*() const {return t;}
private:
const T t;
};
С этим типом внутренний T
будет неизменным независимо от того, как пользователь объявляет свои immutable<T>
. Теперь класс C
должен взять immutable<Params>
на const&
. И поскольку immutable<T>
невозможно построить из копии или перемещения существующего T
, пользователь вынужден использовать immutable<Params>
всякий раз, когда он хочет передать это как параметр.
Конечно, ваша самая большая опасность состоит в том, что они пройдут мимо временного. Но это была проблема, которую вам уже нужно было решить.
Ответ 2
Я не знаю причину, но вот как вы можете это сделать:
struct C {
template<typename T, typename T2>
C(T&&, const T2&&) = delete;
C(const Params&, const DataBuffer&) { /*...*/ }
};
Объявляя конструктор, который принимает любой аргумент с помощью ссылки non-const, он всегда будет лучше, чем конструктор, принимающий const&
, так как не нужно добавлять cv-квалификатор.
Конструктор const&
является лучшим совпадением при передаче параметров const
, поскольку cv-квалификатор не нужно удалять.
DataBuffer db;
const Params cp;
C c{ cp, db }; // ok, second constructor call is chosen
Params p;
C c2{ p, db }; // error, constructor is deleted
Заметим, что @IgorTandetnik сказал, вы можете легко сломать свое требование:
Params pa;
const Params& ref_pa = pa;
C c3{ ref_pa, db }; // ok, but shouldn't compile.
Ответ 3
Что вам нужно, это не ссылка const, а объект const. Ценностная семантика решает вашу проблему. Никто не может изменять объект const. Хотя ссылка только const, где она помечена как const, поскольку ссылочный объект не может быть const. Возьмите это, например:
int a;
int const& b = a;
// b = 4; <-- not compiling, the reference is const
// (1)
В (1)
, a
- int, а b
- ссылка на const int. В то время как a
не является константой, язык разрешает привязку ссылки на const к объекту non const. Таким образом, это ссылка на объект const, связанный с изменяемым объектом. Система типов не позволит вам изменять изменяемый объект через ссылку, потому что это может быть связано с объектом const. В нашем случае это не так, но племя не меняется. Однако даже объявление ссылки на const не изменит первоначальную декларацию. Int a
все еще является изменяемым объектом. Я могу заменить комментарий (1)
следующим образом:
a = 7;
Это действительно, когда объявлены ссылки или другие переменные. Изменчивый int может меняться, и ничто не может помешать ему измениться. Черт, даже другая программа, такая как чит-движок, может изменить значение изменяемой переменной. Даже если у вас есть правила на языке, чтобы гарантировать, что он не будет изменен, нет ничего, что предотвратит изменение изменчивой переменной. На любом языке. В машинном языке изменяемое значение разрешено изменять. Однако, возможно, некоторые API операционной системы могут помочь вам изменить изменчивость областей памяти.
Что вы можете сделать, чтобы решить эту проблему сейчас?
Если вы хотите быть на 100% уверенным, что объект не будет изменен, вы должны иметь неизменные данные. Обычно вы объявляете неизменяемые объекты с помощью ключевого слова const
:
const int a = 8;
int const& b = a;
// a cannot change, and b is guaranteed to be equal to 8 at this point.
Если вы не хотите, чтобы a
был неизменным и все еще гарантировал, что b
не изменится, используйте значения вместо ссылок:
int a = 8;
const int b = a;
a = 9;
// The value of b is still 8, and is guaranteed to not change.
Здесь значение sematic может помочь вам иметь то, что вы хотите.
Тогда ссылка const есть для чего? Там есть, чтобы выразить то, что вы собираетесь делать со ссылкой, и помогите обеспечить, что может измениться там.
Поскольку вопрос был дополнительно уточнен, нет никакого способа определить, была ли ссылка привязана к изменяемому или неизменяемому объекту в первую очередь. Тем не менее, есть некоторые трюки, которые вы можете отличить от изменчивости.
Вы видите, что если вы хотите получить дополнительную информацию об изменчивости вместе с экземпляром, вы можете сохранить эту информацию в типе.
template<typename T, bool mut>
struct maybe_immutable : T {
using T::T;
static constexpr auto mutable = mut;
};
// v--- you must sync them --v
const maybe_immutable<int, false> obj;
Это самый простой способ его реализации, но и наивный. Содержащиеся данные будут условно неизменными, но это вынуждает вас синхронизировать параметр и константу шаблона. Однако решение позволяет вам сделать это:
template<typename T>
void do_something(const T& object) {
if(object.mutable) {
// initially mutable
} else {
// initially const
}
}
Ответ 4
Как и предыдущие ответы, С++ не имеет понятия "непреложный". @Rakete1111 дал вам ответ, который я бы использовал. Однако Visual Studio поместит глобальную константную переменную в сегмент .rdata, где другие переменные перейдут в .data. Сегмент .rdata генерирует ошибку при попытке записи.
Если вам нужен тест времени выполнения, только если объект доступен только для чтения, используйте обработчик сигнала, например:
#include <csignal>
const int l_ci = 42;
int l_i = 43;
class AV {};
void segv_handler(int signal) {
throw AV{};
}
template <typename T>
bool is_mutable(const T& t)
{
T* pt = const_cast<int*>(&t);
try {
*pt = T();
}
catch (AV av) {
return false;
}
return true;
}
void test_const()
{
auto prev_handler = std::signal(SIGSEGV, segv_handler);
is_mutable(l_i);
is_mutable(l_ci);
}
Ответ 5
Надеюсь, я понимаю, что ваш вопрос правильный, он не так ясен, как сказать "D-язык", но со ссылками на константу r-value вы можете сделать неизменные параметры.
То, что я понимаю из неизменяемого, является, например,
void foo ( const int&& immutableVar );
foo(4);-> is ok
int a = 5;
foo(a);->is not ok