const правильность и возвращаемые значения - 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&. Насколько мне известно, тип 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;
}

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


person Navaneeth K N    schedule 20.02.2010    source источник


Ответы (3)


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

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

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

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

Обновить

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

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

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

const int c = 5;
int n = c;

Здесь у меня были некоторые константные данные в c, и я скопировал данные в неконстантную переменную n. Это нормально, он просто читает данные. Значение в c не было изменено.

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

struct foo { int n; };

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

Обновление 2:

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

const T& value() const

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

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

person Daniel Earwicker    schedule 20.02.2010
comment
Спасибо. Но я все еще недостаточно ясен. По мнению ИМО, value() возвращает const T&, который может быть назначен только другому типу const. Но в этом случае компилятор спокойно разрешает преобразование. Также в моем случае я не могу сделать test<const foo*>. - person Navaneeth K N; 20.02.2010
comment
@Appu В этом разница между указателем на константу и константным указателем на неконстанту. Понятия не имею, почему вы не можете сделать test<const foo*>. - person amit; 20.02.2010
comment
Пожалуйста, смотрите редактирование. У меня есть неконстантный член f и из него возвращается const. В этом случае компиляция жалуется правильно. Похоже, поведение отличается, когда используются шаблоны. - person Navaneeth K N; 20.02.2010
comment
@Appu: когда вы создаете экземпляр своего шаблона с помощью foo*, тогда const T& является const (foo *)& -- константной ссылкой на указатель на неконстантный. ссылка — это константа, а не указатель. В вашем примере без шаблона используется другой тип, const foo *. - person Nick Meyer; 20.02.2010
comment
Ах, теперь я понял. Спасибо за подробное объяснение. Я очень ценю это. Итак, вы думаете, что частичная специализация для типа указателя — это способ решить эту проблему? - person Navaneeth K N; 20.02.2010
comment
Это полностью зависит от ситуации. Если это большой сложный шаблон, специализация может включать в себя много дублирования — лучше с самого начала разработать систему шаблонов с учетом специализации. Может быть, если бы вы предоставили более подробную информацию о более широкой проблеме - или, может быть, это должно быть в других вопросах. - person Daniel Earwicker; 20.02.2010
comment
@Earwicker, поскольку вы очень осторожны с языком здесь, я бы посоветовал вам сказать, что возвращаемое значение будет ссылкой на константный указатель на неконстантный foo. Я знаю, что вы используете фразу ссылка на константу небрежно, чтобы обозначить ссылку на константу, но поскольку суть этой путаницы заключается в том, куда идут константы, может быть полезно быть экстрапедантичным. - person Jesse Beder; 20.02.2010
comment
@ Джесси - правда, это ленивая привычка. Пока читатель знает, что все ссылки являются константами в этом другом смысле, это не может быть неверно истолковано: это может быть только сокращением для ссылки на константу, но если это не так, я думаю, это может легко вызвать дальнейшую путаницу. в подобных обсуждениях. - person Daniel Earwicker; 20.02.2010

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

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

    test() {}

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

private:
    T* f;
};

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

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

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

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

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

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

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

person Manuel    schedule 20.02.2010