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