правильность и возвращаемые значения - C ++ - PullRequest
0 голосов
/ 20 февраля 2010

Пожалуйста, рассмотрите следующий код.

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

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

Ответы [ 3 ]

6 голосов
/ 20 февраля 2010

Поскольку у вас есть два уровня косвенности - в вашей основной функции этот вызов 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 () возвращает const T &, который может быть назначенным только другому тип. Но в этом случае компилятор безопасно разрешая преобразование.

Данные Const можно только читать. Это не может быть написано ("видоизменено"). Но копирование некоторых данных - это способ чтения, так что все в порядке. Например:

const int c = 5;
int n = c;

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

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

struct foo { int n; };

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

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

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

const T& value() const

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

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

1 голос
/ 20 февраля 2010

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

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

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

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

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

1 голос
/ 20 февраля 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*’
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...