2010-02-20 4 views
0

Обратите внимание на следующий код.Константа корректность и возвращаемые значения - C++

struct foo 
{ 
}; 

template<typename T> 
class test 
{ 
public: 

    test() {} 

    const T& value() const 
    { 
     return f; 
    } 

private: 
    T f; 
}; 


int main() 
{ 
    const test<foo*> t; 
    foo* f = t.value(); 
    return 0; 
} 

t является const переменной и value() является постоянным членом-функция, которая возвращает const T&. AFAIK, тип const не присваивается неконстантическому типу. Но как foo* f = t.value(); хорошо компилируется. Как это происходит и как я могу гарантировать, что value() может быть назначен только const foo*?

Редактировать

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

class test 
{ 
public: 

    test() {} 

    const foo* value() const { return f; } 

private: 
    foo* f; 
}; 


int main() 
{ 
    const test t; 
    foo* f = t.value(); // error here 
    return 0; 
} 

Почему проблема возникает при использовании шаблонов?

ответ

6

Поскольку у вас есть два уровня косвенности - в вашей основной функции этот вызов value возвращает ссылку на указатель const на неконстантный foo.

Это можно безопасно скопировать в неконстантный указатель на неконстантный foo.

Если вы создали экземпляр test с const foo *, это будет другая история.

const test<const foo*> t; 
foo* f = t.value(); // error 
const foo* f = t.value(); // fine 
return 0; 

Update

Из комментария:

значение() возвращает сопзЬ T &, которые могут быть назначены только на другой сопзЬ типа. Но в этом случае компилятор безопасно разрешает преобразование.

Данные константы могут быть прочитаны. Он не может быть записан («мутирован»). Но копирование некоторых данных - это способ их чтения, так что все в порядке. Например:

const int c = 5; 
int n = c; 

Здесь я имел некоторые константные данные в c, и я скопировать данные в неконстантный переменной п. Все в порядке, это просто чтение данных. Значение в c не было изменено.

Теперь предположим, что ваши foo имели некоторые данные в нем:

struct foo { int n; }; 

Если у меня есть неконстантную указатель на одну из тех, что я могу изменить значение n через указатель. Вы спросили свой шаблон test для хранения указателя на неконстантный foo, а затем сделали экземпляр const из test. Поэтому всегда указывается адрес указателя. Никто не может изменить адрес, хранящийся в указателе внутри test, поэтому его нельзя указать на другой объект. Однако объект, на который он указывает, может изменить его содержимое.

Update 2:

Когда вы сделали свою версию без шаблона из примера, вы сделали ошибку. Чтобы все было правильно, вам нужно заменить foo * на каждое место, где есть T.

const T& value() const 

Обратите внимание, что у вас есть ссылка на const T. Таким образом, возвращаемое значение будет ссылкой на что-то const: a foo *. Только адрес указателя не может быть изменен. Объект, на который он указывает, может изменить его содержимое.

В вашем втором примере вы избавились от ссылочной части, которая изменяет значение и делает модификатор const применимым к объекту, на который указывает указатель, вместо того, чтобы применять его к самому указателю.

+0

Спасибо. Но я все еще недостаточно ясен. IMO-мнение, 'value()' возвращает 'const T &', который может быть назначен только другому типу 'const'. Но в этом случае компилятор безопасно разрешает преобразование. Также в моем случае я не могу выполнить 'test '. –

+0

@Appu Это отличается от указателя на const и const указателем на non-const. Не знаю, почему вы не можете «сделать» 'test '. –

+0

См. Редактирование. У меня есть неконстантный член 'f' и возвращающий' const' из него. В этом случае компиляция испрашивается правильно. Похоже, что поведение отличается при использовании шаблонов. –

1

Используйте следующий шаблон специализации:

template<typename T> 
class test<T*> 
{ 
public: 

    test() {} 

    const T* value() const 
    { 
     return f; 
    } 

private: 
    T* f; 
}; 

После включения этого г ++ говорит:

d.cpp: In function ‘int main()’: 
d.cpp:41: error: invalid conversion from ‘const foo*’ to ‘foo*’ 
1

Там нет ничего плохого в вашем коде, имеющий константную ссылку на указатель означает только то, что вы можете 't измените указатель, но объект с указателем остается полностью изменчивым. Если внутри вашей функции main вы пытаетесь изменить адрес, на который указывает член ft, вы увидите, что вы не можете: инкапсуляция полностью сохраняется.

Это тот же принцип, что делает следующий код правильный:

void foo(std::vector<int *> const & v) 
{ 
    *v[0] = 0; // op. [] returns const & to int * 
} 

Люди знакомы с C++, как правило, удивлены таким поведением, потому что для них константный вектор не должен допускать изменения его элементов. И на самом деле это не так, потому что указатель, хранящийся в векторе, не изменяется (он продолжает указывать на тот же адрес). Это объект с заостренным объектом, который изменен, но вектор не заботится об этом.

Единственное решение - сделать, как говорит Амит, и предоставить специализацию вашего класса для T*.

Смежные вопросы