2013-06-13 4 views
0

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

Поскольку класс имеет ссылочную семантику, он обычно передается по значению, как указатель. Это приводит к проблеме с корректностью 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

Вы можете сделать конструктор копирования закрытым. –

+0

@JonathanPotter: Тогда никто не мог его скопировать по любой причине. –

+0

Как удалить конструктор копирования и создать конструктор, который принимает неконстантные ссылки? – syam

ответ

0

Что вы спрашиваете, не представляется возможным. Это не возможно, чтобы эти 2 линии ведут себя по-разному:

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

К сожалению. Я обновил свой код выше. 'Ref & bar = (Ref &)c;' будет просто ссылаться на const-объект на неконстантную ссылку на этот объект const. Нет предупреждения. –

+0

Это как сказать, что 'const'-correctness не существует, потому что кто-то всегда может' const' (или в вашем случае, в стиле C). Это преднамеренное неправильное использование, а не случайное злоупотребление, которое предотвращает «int & bar (r)». –

+0

Это один из моих моментов. Почему OP пытается предотвратить это? Он просто пытается спасти себя от себя или пытается помешать беспринципному человеку обойти его безопасность? Это много усилий, чтобы спасти себя от себя. –

2

Это точно такая же проблема, которая возникает из-за слияния const T* и T* const: изменчивость ссылки и референта различна , В C++ существуют допустимые варианты использования для всех четырех возможных комбинаций. Я хотел бы сделать различные типы для «ссылки на 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. 
} 

Live example at ideone.

+0

Это класс Pimpl, он не раскрывает своих рядовых как своего рода аргумент шаблона. –

+0

@NicolBolas: точка зрения на то, что это самый простой для реализации с отдельными типами для ссылки на константу и ссылки на изменяемые, с привязкой к-mutable, неявно конвертируемой в reference-to-const. Я реализовал два типа простых способов с помощью шаблона, вам могут понадобиться два явных типа. ? – Casey

+0

'Ref & бар = (Ref &)c;' –

0

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; 
} 

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

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

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