Const Struct &

У меня небольшие проблемы с определением того, как именно 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 () компилируется?

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

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
8
0
1 101
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

unchangeable.AccessWidgets ():

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

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

Новые виджеты не создаются в WidgetHolder; члены являются ссылками, а аргументы конструктора - копиями.

Patrick Johnmeyer 12.09.2008 21:09

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

Brian R. Bondy 14.09.2008 00:15

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

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

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

В качестве альтернативы передача указателей (не ссылок) допустима, если вы даете явные гарантии времени жизни и владения этими объектами. Если вы передаете ссылки, WidgetHolder не сможет «владеть» объектами.

hazzen 12.09.2008 02:45
Ответ принят как подходящий

Это компилируется, потому что, хотя WidgetHolder является константным объектом, эта константа не применяется автоматически к объектам, на которые указывает (ссылается) 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){}

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

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


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;

Обновлено: он удалил свой ответ, из-за чего я выгляжу немного глупо :)

Ответ Flame опасно неверен. Его 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) {}

  /* ... */

};

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

#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 и none const аксессор для каждого виджета.

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

Другие вопросы по теме