Const Struct & - PullRequest
       26

Const Struct &

8 голосов
/ 11 сентября 2008

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

struct Widget
{
    Widget():x(0), y(0), z(0){}

    int x, y, z;
};

struct WidgetHolder //Just a simple struct to hold four Widgets.
{
    WidgetHolder(Widget a, Widget b, Widget c, Widget d): A(a), B(b), C(c), D(d){}

    Widget& A;
    Widget& B;
    Widget& C;
    Widget& D;
};

class Test //This class uses four widgets internally, and must provide access to them externally.
{
    public:
        const WidgetHolder AccessWidgets() const
        {
            //This should return our four widgets, but I don't want anyone messing with them.
            return WidgetHolder(A, B, C, D);
        }

        WidgetHolder AccessWidgets()
        {
            //This should return our four widgets, I don't care if they get changed.
            return WidgetHolder(A, B, C, D);
        }

    private:
        Widget A, B, C, D;
};

int main()
{
    const Test unchangeable;

    unchangeable.AccessWidgets().A.x = 1; //Why does this compile, shouldn't the Widget& be const?
}

По сути, у меня есть класс с именем test. Он использует четыре виджета для внутреннего использования, и мне нужно, чтобы он возвращал их, но если test был объявлен как const, я хочу, чтобы виджеты также возвращали const.

Может кто-нибудь объяснить мне, почему код в main () компилируется?

Большое спасибо.

Ответы [ 6 ]

7 голосов
/ 11 сентября 2008

Вам необходимо создать новый тип специально для хранения константных виджетов и объектов. То есть:


struct ConstWidgetHolder
{
    ConstWidgetHolder(const Widget &a, const Widget &b, const Widget &c, const Widget &d): A(a), B(b), C(c), D(d){}

    const Widget& A;
    const Widget& B;
    const Widget& C;
    const Widget& D;
};

class Test
{
public:
    ConstWidgetHolder AccessWidgets() const
    {
        return ConstWidgetHolder(A, B, C, D);
    }

Теперь вы получите следующую ошибку (в gcc 4.3):

widget.cc: In function 'int main()':
widget.cc:51: error: assignment of data-member 'Widget::x' in read-only structure

Аналогичная идиома используется в стандартной библиотеке с итераторами:


class vector {
    iterator begin();
    const_iterator begin() const;

3 голосов
/ 11 сентября 2008

Ваш WidgetHolder будет содержать недействительные ссылки (указатели). Вы передаете объекты в стеке конструктору, а затем сохраняете ссылки на их (временные) адреса. Это гарантированно сломается.

Вы должны назначать ссылки только тем объектам с тем же (или большим) временем жизни, что и сама ссылка.

Передайте ссылки конструктору, если вы должны хранить ссылки. Более того, не держите ссылки вообще, а просто делайте копии.

3 голосов
/ 11 сентября 2008

unchangeable.AccessWidgets ():

На данный момент вы создаете новый объект типа WidgetHolder. Этот объект не защищен const.

Вы также создаете новые виджеты в WidgetHolder, а не ссылки на Wdiget.

2 голосов
/ 11 сентября 2008

Это компилируется, потому что, хотя WidgetHolder является объектом const, это постоянство не применяется автоматически к объектам, на которые ссылается WidgetHolder (на которые ссылается). Подумайте об этом на машинном уровне - если сам объект WidgetHolder будет храниться в постоянной памяти, вы все равно сможете писать вещи, на которые указывал WidgetHolder.

Проблема заключается в следующей строке:

WidgetHolder(Widget a, Widget b, Widget c, Widget d): A(a), B(b), C(c), D(d){}

Как уже упоминал Фрэнк, ваши ссылки внутри класса WidgetHolder будут содержать недопустимые ссылки после возврата конструктора. Поэтому вы должны изменить это на:

WidgetHolder(Widget &a, Widget &b, Widget &c, Widget &d): A(a), B(b), C(c), D(d){}

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

0 голосов
/ 15 сентября 2008

Первоначальный запрос заключался в том, как вернуть WidgetHolder как const, если содержащий класс был const. C ++ использует const как часть сигнатуры функции, и поэтому у вас может быть одна и та же функция в версиях const и no const. Неконстантный вызывается, когда экземпляр не является константным, а константный вызывается, когда экземпляр является константным. Поэтому решение состоит в том, чтобы получить доступ к виджетам в держателе виджета по функциям, а не напрямую. Ниже я приведу более простой пример, который, как мне кажется, отвечает на первоначальный вопрос.

#include <stdio.h>

class Test
{
public:
  Test(int v){m_v = v;}
 ~Test(){printf("Destruct value = %d\n",m_v);}

 int& GetV(){printf ("None Const returning %d\n",m_v); return m_v;  }

 const int& GetV() const { printf("Const returning %d\n",m_v); return m_v;}
private:
  int m_v;
};

void main()
{
  // A none const object (or reference) calls the none const functions
  // in preference to the const
  Test one(10);
  int& x = one.GetV();
  // We can change the member variable via the reference
  x = 12;

  const Test two(20);
  // This will call the const version  
  two.GetV();

  // So the below line will not compile
  // int& xx = two.GetV();

  // Where as this will compile
  const int& xx = two.GetV();

  // And then the below line will not compile
  // xx = 3;

}

Что касается исходного кода, я думаю, что было бы проще иметь WidgetHolder в качестве члена класса Test, а затем возвращать либо const, либо no const ссылку на него, и делать Widgets частными членами владельца и предоставьте метод доступа const и none для каждого виджета.

class WidgetHolder {
...

Widget& GetA();
const Widget& GetA() const;
...
};

А потом по основному классу

class Test {
...
WigetHolder& AccessWidgets() { return m_Widgets;}
const WidgetHolder&AcessWidgets() const { return m_Widgets;}

private:
  WidgetHolder m_Widgets;
...
};
0 голосов
/ 12 сентября 2008

РЕДАКТИРОВАТЬ: он удалил свой ответ, заставляя меня выглядеть немного глупо:)

Ответ Пламени опасно неправильный. Его WidgetHolder принимает ссылку на объект значения в конструкторе. Как только конструктор вернется, этот переданный по значению объект будет уничтожен, и вы будете хранить ссылку на уничтоженный объект.

Очень простой пример приложения, использующего его код, ясно показывает это:

#include <iostream>

class Widget
{
    int x;
public:
    Widget(int inX) : x(inX){}
    ~Widget() {
    std::cout << "widget " << static_cast< void*>(this) << " destroyed" << std::endl;
     }
};

struct WidgetHolder
{
    Widget& A;

public:
    WidgetHolder(Widget a): A(a) {}

    const Widget& a() const {
    std::cout << "widget " << static_cast< void*>(&A) << " used" << std::endl;
    return A;
}

};

int main(char** argv, int argc)
{
Widget test(7);
WidgetHolder  holder(test);
Widget const & test2 = holder.a();

return 0;
} 

Вывод будет что-то вроде

widget 0xbffff7f8 destroyed
widget 0xbffff7f8 used
widget 0xbffff7f4 destroyed

Чтобы избежать этого, конструктор WidgetHolder должен принимать ссылки на переменные, которые он хочет сохранить в качестве ссылок.

struct WidgetHolder
{
    Widget& A;

public:
    WidgetHolder(Widget & a): A(a) {}

  /* ... */

};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...