Pergunta

Estou tendo problemas para descobrir exatamente como const se aplica em um caso específico.Aqui está o código que tenho:

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

Basicamente, tenho uma classe chamada teste.Ele usa quatro widgets internamente e preciso retorná-los, mas se o teste foi declarado const, quero que os widgets retornem const também.

Alguém pode me explicar por que o código em main() compila?

Muito obrigado.

Foi útil?

Solução

Isso é compilado porque, embora WidgetHolder seja um objeto const, essa constância não se aplica automaticamente a objetos apontados (referenciados por) WidgetHolder.Pense nisso no nível da máquina - se o próprio objeto WidgetHolder fosse mantido na memória somente leitura, você ainda poderia gravar em coisas que foram apontadas pelo WidgetHolder.

O problema parece estar nesta linha:

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

Como Frank mencionou, suas referências dentro da classe WidgetHolder conterão referências inválidas após o retorno do construtor.Portanto, você deve alterar isso para:

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

Depois de fazer isso, ele não será compilado e deixo como exercício para o leitor resolver o resto da solução.

Outras dicas

Você precisa criar um novo tipo especificamente para armazenar objetos const Widget&.Ou seja:


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

Agora você receberá o seguinte erro (no gcc 4.3):

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

Um idioma semelhante é usado na biblioteca padrão com iteradores, ou seja:


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

imutável.AccessWidgets():

Neste ponto, você está criando um novo objeto do tipo WidgetHolder.Este objeto não é protegido por const.

Você também está criando novos widgets no WidgetHolder e não referências ao Wdiget.

Seu WidgetHolder irá conter referências inválidas (ponteiros).Você está passando objetos da pilha para o construtor e, em seguida, mantendo referências aos seus endereços (temporários).É garantido que isso quebrará.

Você só deve atribuir referências a objetos com vida útil igual (ou superior) à da própria referência.

Passe referências ao construtor se você precisar reter referências.Melhor ainda, não guarde as referências e apenas faça as cópias.

EDITAR:ele excluiu sua resposta, me fazendo parecer um pouco tolo :)

A resposta de Flame está perigosamente errada.Seu WidgetHolder faz referência a um objeto de valor no construtor.Assim que o construtor retornar, esse objeto passado por valor será destruído e você manterá uma referência a um objeto destruído.

Um aplicativo de exemplo muito simples usando seu código mostra isso claramente:

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

A saída seria algo como

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

Para evitar isso, o construtor WidgetHolder deve usar referências às variáveis ​​que deseja armazenar como referências.

struct WidgetHolder
{
    Widget& A;

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

  /* ... */

};

A consulta original era como retornar o WidgetHolder como const se a classe que o continha era const.C++ usa const como parte da assinatura da função e, portanto, você pode ter versões const e nenhuma versão const da mesma função.O none const é chamado quando a instância é none const, e o const é chamado quando a instância é const.Portanto, uma solução é acessar os widgets no suporte de widgets por funções, em vez de diretamente.Criei um exemplo mais simples abaixo que acredito que responde à pergunta original.

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

}

Em termos do código original, acho que seria mais fácil ter um WidgetHolder como membro da classe Test e então retornar uma referência const ou none const para ele, e tornar os Widgets membros privados do titular, e fornecer um const e nenhum acessador const para cada widget.

class WidgetHolder {
...

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

E então na aula principal

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

private:
  WidgetHolder m_Widgets;
...
};
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top