| FAQ Lite | ||||||||||||||||||||||||
| Construtores | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
| [ 10.2 ] Há alguma diferença entre List x; e List x(); ? |
| Há uma grande diferença. Suponha que List é o nome de uma classe. A função f() declara um objeto local List denominado x: |
void f()
{
List x; // Local object named x (of class List)
// ...
}
| Mas função q() declara uma função denominada x() que retorna uma List |
void g()
{
List x(); // Function named x (that returns a List)
// ...
}
class Fred {
public:
Fred(); // Default constructor: can be called with no args
// ...
};
| Contudo, é possível, e acontece de fato, que o construtor default admita receber argumentos, definidos com valores default. |
class Fred {
public:
Fred(int i=3, int j=5);
// Default constructor: can be called with no args // ... };
| [ 10.5 ] Qual o construtor chamado quando eu crio uma matriz de objetos Fred ? |
| O construtor default de Fred. Não há modo de se dizer ao compilador para chamar um construtor que não seja o construtor default. Se sua classe Fred não possui um construtor default, a tentativa de criar uma matriz de objetos Fred resulta em um erro em tempo de compilação. |
class Fred {
public:
Fred(int i, int j);
// ... assume there is no default constructor in class Fred ...
};
main()
{
Fred a[10]; // ERROR: Fred doesn't have a default constructor
Fred* p = new Fred[10]; // ERROR: Fred doesn't have a default constructor
}
| Entretanto se você está criando um STL vector<Fred> ao invés de uma matriz de Fred (o que você provavelmente deveria fazer de qualquer modo, já que matrizes são miseráveis), você não precisa ter um construtor default na classe Fred, uma vez que voc6e pode dar ao vector um objeto Fred para ser usado para inicializar os elementos: |
#include <vector>
using namespace std;
main()
{
vector<Fred> a(10, Fred(5,7));
// The 10 Fred objects in vector a will be initialized with Fred(5,7).
// ...
}
class Point {
public:
Point(float x, float y); // Rectangular coordinates
Point(float r, float a); // Polar coordinates (radius and angle)
// ERROR: Overload is Ambiguous: Point::Point(float,float)
};
main()
{
Point p = Point(5.7, 1.2); // Ambiguous: Which coordinate system?
}
| Um modo de resolver essa ambigüidade é usar a técnica Named Constructor Idiom: |
#include <math.h> // To get sin() and cos()
class Point {
public:
static Point rectangular(float x, float y); // Rectangular coord's
static Point polar(float radius, float angle); // Polar coordinates
// These static methods are the so-called "named constructors"
// ...
private:
Point(float x, float y); // Rectangular coordinates
float x_, y_;
};
inline Point::Point(float x, float y)
: x_(x), y_(y) { }
inline Point Point::rectangular(float x, float y)
{ return Point(x, y); }
inline Point Point::polar(float radius, float angle)
{ return Point(radius*cos(angle), radius*sin(angle)); }
| Agora os usuários de Point têm uma sintaxe clara e não-ambígua para criar Points em qualquer dos dois sistema de coordenadas. |
main()
{
Point p1 = Point::rectangular(5.7, 1.2); // Obviously rectangular
Point p2 = Point::polar(5.7, 1.2); // Obviously polar
}
| Certifique-se de que seus construtores estejam na seção protected: se você
espera que Fred tenha classes derivadas. O Named Constructor Idiom pode também ser usado para garantir que seus objetos sejam sempre criados via comando new. |
| [ 10.8 ] Porque não posso inicializar meus dados membros static em minha lista de inicialização do construtor? |
| Porque você precisa definir explicitamente os dados membros static de sua classe. |
Fred.h:
class Fred {
public:
Fred();
// ...
private:
int i_;
static int j_;
};
Fred.cpp (or Fred.C or whatever):
Fred::Fred()
: i_(10) // OK: you can (and should) initialize member data this way
j_(42) // Error: you cannot initialize static member data like this
{
// ...
}
// You must define static data members this way:
int Fred::j_ = 42;
| [ 10.9 ] Porque as classes com dados membros static são passíveis de erros de linker? |
| Porque dados membros static devem ser explicitamente definidos em uma única unidade de compilação. Se você não faz isso, provavelmente obterá um erro do linker undefined external. Por exemplo: |
// Fred.h
class Fred {
public:
// ...
private:
static int j_; // Declares static data member Fred::j_
// ...
};
| O linker vai berrar (Fred::j_ is not defined) a menos que você defina, não meramente declare, Fred::j_ no mesmo mesmo arquivo fonte. |
// Fred.cpp #include "Fred.h" int Fred::j_ = some_expression_evaluating_to_an_int; // Alternatively, if you wish to use the implicit 0 value for static ints: // int Fred::j_;
| O lugar normalmente usado para definir os dados membros static da classe Fred é o arquivo Fred.cpp (ou Fred.c ou qualquer outra extensão de arquivo que você esteja utilizando) |
| [ 10.10 ] O que é o fiasco de seqüência de inicialização static? |
| Uma maneira sutil de matar o seu projeto. O fiasco de seqüência de inicialização static é um aspecto do C++ muito sutil, e normalmente não compreendido. Infelizmente é muito difícil de detectar. Os erros ocorrem antes do início de main() Em poucas palavras: suponha que você tem dois objetos static, x e y, os quais existem em arquivos separados, x.cpp e y.cpp. Suponha ainda que o construtor do objeto y chame algum método do objeto x. É isso. É simples assim. A tragédia, nesse caso, é que você tem 50%-50% chance de morrer. Se acontecer de a unidade de compilação para x.cpp ser inicializada primeiro, tudo estará bem. Mas se a unidade de compilação para y.cpp começar primeiro, então o construtor de y será executado antes do construtor de x, e você estará "ferrado". Ou seja, o construtor de y vai chamar um método do objeto x, antes que o objeto x esteja construido. Se você gosta de jogar Roleta Russa pode interromper sua leitura aqui. Por outro lado, se você gosta de melhorar suas chances de sobrevivência, prevenindo-se sistematicamente contra desastres, você provavelmente vai querer ler o próximo tópico desse FAQ. Nota: O fiasco de seqüência de inicialização static não se aplica a tipos builtin e/ou intrínsecos como int ou char*. Por exemplo, se você cria um objeto static float, nunca terá problema com a seqüência de inicialização. O único caso em que a seqüência de inicialização static é verdadeiramente um fiasco é quando seu objeto static ou global tem um construtor. |
| [ 10.11 ] Como eu me previno contra o fiasco de seqüência de inicialização static? |
| Usando o Construct on First Use Idiom,
que simplemente significa empacotar seu objeto static dentro da função. Por exemplo, suponha que você tem duas classes, Fred e Barney. Há um objeto Fred global chamado x, e um objeto Barney global chamado y. O construtor de Barney invoca o método goBowling() no objeto x. O arquivo x.cpp define o objeto x: // File x.cpp #include "Fred.hpp" Fred x; O arquivo y.cpp define o objeto y; // File y.cpp #include "Barney.hpp" Barney y; Para estar completo, o construtor de Barney deve se parecer com algo como: // File Barney.cpp
#include "Barney.hpp"
Barney::Barney()
{
// ...
x.goBowling();
// ...
}
Como descrito no tópico anterior desse FAQ, o desastre ocorre se y for construido antes de x, o que acontece 50% das vezes, já que estão em arquivos-fonte diferentes. Há várias maneiras de solucionar esse problema, mas a solução mais simples e completamente portável é substituir o objeto global Fred, x , por uma função global x(), que retorna objeto Fred por referência. Uma vez que os objetos locais static são construídos unicamente na primeira vez que o controle passa por sua declaração, o comando new Fred() acima será executado uma única vez: a primeira vez que x() for chamado. Todas as chamadas subseqüentes vão retornar o mesmo objeto Fred (aquele apontado por ans). Assim, tudo o que você faz é trocar o uso de x por x(): // File Barney.cpp
#include "Barney.hpp"
Barney::Barney()
{
// ...
x().goBowling();
// ...
}
Isso é chamado Construct on First Use Idiom porque faz justamente isso: o objeto global Fred é construido na primeira vez em que é usado. O lado negativo dessa abordagem é que o objetoFred nunca é destruido. O C++ FAQ Book apresenta uma segunda técnica que responde a esse problema, mas ao custo de incorrer no fiasco de seqüência de des-inicialização static. Nota: Você não tem esse problema com tipos built-in e/ou intrínsecos como int ou char*. Por exemplo, se você cria um objeto float global ou static, não há necessidade de empacotá-lo dentro da função. O único caso em que a seqüência de inicialização static é verdadeiramente um fiasco é quando seu objeto static ou global tem um construtor. |
| [ 10.12 ] Como eu me previno contra o fiasco de seqüência de inicialização static para os meus dados membros static? |
| Usando a mesma técnica descrita no tópico
anterior desse FAQ, mas dessa vez use uma função membro static ao invés de uma
função global. Suponha que você tem uma classe x que possui um objeto static Fred: // File X.hpp
class X {
public:
// ...
private:
static Fred x_;
};
Naturalmente o membro static é inicializado separadamente: // File X.cpp #include "X.hpp" Fred X::x_; Naturalmente também, o objeto Fred será usado em um ou mais métodos de x: void X::someMethod()
{
x_.goBowling();
}
Mas aqui o perigo é alguém, de algum lugar, de alguma maneira chamar este método antes que o objeto Fred esteja construído. Por exemplo, se alguem cria um objeto static x e invoca seu método someMethod() durante a inicialização static, então você fica mercê do compilador, de como o compilador irá construir X::x_, antes ou após someMethod() ser chamado. (Note que o comitê ANSI/ISO C++ está trabalhando nesse problema, mas ainda não estão disponíveis os compiladores com as alterações necessárias para tratar esse problema. Visite esse tópico no futuro para verificar se houve alterações). De qualquer maneira, é sempre mais portável e mais seguro alterar o dado membro X::x_ para a função membro static: // File X.hpp
class X {
public:
// ...
private:
static Fred& x();
};
Naturalmente este membro static é inicializado separadamente: // File X.cpp
#include "X.hpp"
Fred& X::x()
{
static Fred* ans = new Fred();
return *ans;
}
Então você simplesmente troca o uso de x_ por x(): void X::someMethod()
{
x().goBowling();
}
Nota: Você não tem que fazer isso para tipos built-in e/ou intrínsecos como int ou char*. Por exemplo, se você cria um objeto float global ou static, não há necessidade de empacotá-lo dentro da função. O único caso em que a seqüência de inicialização static é verdadeiramente um fiasco é quando seu objeto static ou global tem um construtor. |
|
| | Home | Bookmarks | Universidades | Para Saber mais | Universidades | WEB Directory | Mapa do site | | |