Les constructeurs sont une sorte de fonction "init". Ils transforment une pile de bits arbitraire en un objet vivant. Au minimum ils initialisent les champs de l'objet. Ils peuvent également allouer des ressources (mémoire, fichiers, sémaphores, sockets, etc..).
"ctor" est une abréviation typique pour le mot "constructeur".
[ Haut | Bas | Rechercher ]
Supposez que list soit le nom d'une certaine classe. Si la fonction f() déclare un objet local de type list et appelé x:
void f() { list x; // objet local nommé x (de la classe list) // ... }
La fonction g() en revanche déclare une fonction appelée x() qui retourne un objet de type list:
void g() { list x(); // fonction nommé x (qui retourne une list) // ... }
[ Haut | Bas | Rechercher ]
Prenons un exemple: Imaginons que vous vouliez que le contructeur Foo::Foo(char) appele le constructeur Foo:Foo(char, int) dans le but d'initialiser l'objet this. Malheureusement il n'y a pas de moyen de faire cela en C++.
Certains le font. Malheureusement cela ne fait pas ce qu'ils veulent. Par exemple, la ligne Foo(x,0); n'appelle pas Foo::Foo(char,int) sur this. Ce que cela fait c'est de creer un objet temporaire avec le constructeur Foo::Foo(char, int), mais cet objet est detruit immediatement apres le ';'.
class Foo { public: Foo(char x); Foo(char x, int y); ... }; Foo::Foo(char x) { ... Foo(x, 0); // this line does NOT help initialize the this object!! ... }Vous pouvez combiner les deux constructeurs en utilisant un paramètre par défaut.
class Foo { public: Foo(char x, int y=0); // this line combines the two constructors ... };Si cela ne vous conviens pas, par exemple il n'y a pas de valeur par defaut possible, vous pouvez partager leur code commun dans une fonction membre privée.
class Foo { public: Foo(char x); Foo(char x, int y); ... private: void init(char x, int y); }; Foo::Foo(char x) { init(x, int(x) + 7); ... } Foo::Foo(char x, int y) { init(x, y); ... } void Foo::init(char x, int y) { ... }
[ Haut | Bas | Rechercher ]
class Fred { public: Fred(); // constructeur par défaut: peut s'appeler sans args // ... };
Toutefois il est possible (et probable) qu'un constructeur par défaut prenne des arguments, s'ils sont spécifiés par défaut:
class Fred { public: Fred(int i=3, int j=5); // constructeur par défaut: peut s'appeler sans args // ... };
[ Haut | Bas | Rechercher ]
Il n'y a aucun moyen de demander au compilateur d'appeler un constructeur différent. Si votre class Fred n'a pas de constructeur par défaut, une tentative de créer un tableau de Fred, se soldera par une erreur de compilation.
classe Fred {
public:
Fred(int i, int j);
/ / ... supposent
qu' il n'y a aucun constructeur de défaut dedans classe
Fred ...
};
main()
{
Fred a[10 ];
/ / ERREUR: Fred n'a pas un constructeur par défaut
Fred * p = new Fred[10
]; / / ERREUR: Fred n'a pas un constructeur
de défaut
}
Cependant si vous créez un vector<Fred STL plutôt qu'un tableau standard de Fred (ce que vous devriez faire de toute façon puisque les tableaux sont mauvais ), vous n'avez plus besoin d'avoir un constructeur par défaut dans class Fred, puisque vous passez un objet Fred au vector pour initialiser les éléments:
#include <vector
using
namespace std;
main()
{
vector<Fred a(10, Fred(5,7));
/ / 10 Fred objets dans le vecteur a
seront initialisé avec Fred(5,7).
/ / ...
}
[ Haut | Bas | Rechercher ]
Par exemple, ce constructeur initialise l'objet membre x_ en utilisant une liste d'initialisation: Fred::Fred() : x_(quoiquecesoit) { }. D'un point de vue exécution, il est important de noter que l'expression quoiquecesoit ne générera pas nécessairement la création d'un objet séparé et sa copie dans x_: si les types sont identiques le résultat de ... quoiquecesoit... sera construit directement à dans x_.
En revanche le constructeur suivant utilise l'affectation: Fred::Fred() { x _ = quoiquecesoit; }. Dans ce cas-ci l'expression quoiquecesoit contraint la création d'un objet séparé et provisoire, passé ensuite a l'opérateur d'assignation de x_, avant d'être détruit au ;. C'est inefficace.
Il y a encore une autre source d'inefficacité : dans le deuxième cas (l'affectation), le constructeur par défaut de l'objet (implicitement appelé devant le corps du constructeur "{") pourrait, par exemple, assigner une quantité de mémoire par défaut ou ouvrir un fichier par défaut. Tout ce travail pourrait être pour rien si l'expression quoiquecesoit et/ou l'opérateur d'affectation donnait lieu a la fermeture du fichier et/ou la libération de cette mémoire (par exemple, si le constructeur par défaut n'assignait pas un bloc de mémoire assez grand ou s' il ouvrait le mauvais fichier).
Conclusion: toutes choses égales par ailleurs, votre code tournera plus vite si vous utilisez les listes d'initialisation plutôt que l'assignation.
_PAR([[ Note: Il n'y a pas de différence de performance si le type de x_ est de base, comme int, ou char *, ou float. Mais même dans ce cas, ma préférence personnelle est d'initialiser ses données dans la liste d'initialization plutô que par affectation par soucis de consitence. Un autre argument lié à a la symetrie: la valeur des membres de données const et non statiques ne peuvent pas être modifiées dans le constructeur, donc pour conserver la symetrie, je recommende d'initialiser tout dans la list d'initialisation.[ Haut | Bas | Rechercher ]
Certains pensent qu'on ne devrait pas utiliser le pointeur this dans un constructeur parce que l'objet n'est pas completement formé. Pourtant il possible d'utiliser le pointeur this dans le corp du constructeur et même dans la liste d'initializaton si on est prudent.
Voilà quelque chose qui fonctionne toujours : le (corps du) constructeur (ou une fonction appelée depuis le constructeur) peuvent acceder aux membres de donnée déclarés dans une classe de base et/ou aux membres de donnée déclarés dans la classe elle-même en toute fiabilité. C'est parce que tous ces membres de donnée sont garantis avoir été completement construits au moment ou le (corps du) constructeur commence à être executer.
Voilà quelque chose qui ne fonctionne jamais : le (corps du) constructeur (ou une fonction appelée par lui) ne peut pas descendre dans une classe dérivée en appelant une méthode virtual qui est redéfinie dans une classe dérivée. Si votre but était d'executer le code de la fonction virtuelle, ça ne fonctionnera pas. Notez que vous n'obtiendrez pas la version de la classe dérivée indépendemment de la manière d'appeler la fonction membre virtuelle : en utilisant explicitement this (e.g. this->method(), ou implicitement sans utiliser le pointeur this (e.g.method()), ou même en appelant quelque autre fonction qui appelle la fonction membre virtuelle en question a partir du pointeur this. La clé est que même si l'appeleur est en train de construire un objet d'un type dérivé, pendant la construction de la classe de base, votre objet n'appartient pas encore à cette classe dérivée. Vous êtes prevenus.
Voilà quelque chose qui fonctionne parfois: si vous passez n'importe quel membre de donnée de l'objet à au constructeur d'initialisation d'un autre membre de donnée, vous devez vous assurer que l'autre membre de donnée a déjà été initialisé. La bonne nouvelle est que vous pouvez déterminer si l'autre membre de donnée a (ou non) déjà été initialisé en utilisant des règles du langage indépendantes du compilateur que vous utilisez. La mauvaise nouvelle est qu'il vous faut connaître ces règles (e.g. les sous-objets de la classe de base sont initialisés en premier (vérifier l'ordre si vous avez de l'héritage multiple et/ou de l'héritage virtuel!), ensuite viennent les membres de donnée définis dans la classe qui est initialisée dans l'ordre dans lequel ils apparaissent dans la déclaration de la classe). Si vous ne connaissez pas ces règles alors ne passez aucun membre de donnée depuis l'objet this (cela ne dépend pas de l'utilisation explicite de this->) vers l'initialiseur d'un autre membre de donné! Et si vous connaissez ces règles, s'il vous plait faites attention.
[ Haut | Bas | Rechercher ]
Le problème est que les constructeurs ont toujours le même nom que la classe. Par conséquent la seule voie de différencier entre les divers constructeurs d'une classe se fait via la liste de paramètres. Mais s' il y a beaucoup de constructeurs, les différences entre les constructeurs devient quelque peu subtile et sujette a erreur.
Avec l'idiom du constructeur nommé, vous déclarez les constructeurs de toute la classe dans l'une des sections private: ou protected:. Vous fournissez des méthodes declarée static dans la section public: qui renvoient un objet. Ces méthodes statiques sont connus comme "constructors nommé". En général il y a une telle méthode statique pour chaque manière différente de construire l'objet.
Par exemple, supposez que nous construisions une classe Point qui représente une position sur le plan X/Y. Il s'avère qu'il y a deux façon d'indiquer une coordonnée dans un espace bi-dimensionel : coordonnées rectangulaires (X+Y), coordonnées polaires (Distance+Angle). (ne vous inquiétez pas si vous ne pouvez pas vous rappeler ces derniers; les conditions particulières des systèmes de coordonnées représenatant un point n'importent pas; l'important est qu'il y a plusieurs façons de créer un point). Malheureusement les paramètres pour ces deux systèmes de coordonnées ont identiques: deux réels. Ceci créerait une ambiguïté dans les constructeurs surchargés:
class Point {
public:
Point(float x, float y); // Coordonnées rectangulaires
Point(float r, float
a); // Coordinnées polaires (distance et angle)
// ERROR: Surcharge
ambiguë: Point::Point(float,float)
};
main()
{
Point p = Point(5.7, 1.2); // Ambigu: De quel système de
coordonnées parle-t-on?
}
Une manière de résoudre cette ambiguïté est d'utiliser l'idiom du constructeur nommé:
#include <math.h // Pour avoir sin() et cos()
class Point {
public:
static Point rectangular(float x, float y);
// Coords rectangulaires
static Point polar(float radius, float angle); //
Coords polaires
// Ces méthodes static sont les "constructeurs només"
// ...
private:
Point(float x, float y);
// coordonnées rectangulaires
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)); }
Maintenant les utilisateurs du point ont une syntaxe claire et non ambiguë; pour créer des points dans l'un ou l'autre système de coordonnées:
main()
{
Point p1 = Point::rectangular(5.7, 1.2); // Evidemment rectangulaire
Point p2 = Point::polar(5.7,
1.2); // Evidemment polaire
}
Faîtes attention à déclarer vos constructeurs dans la section protected: si vous vous attendez à ce que Fred ait des classes dérivées.
L'idiom du constructeur nommé peut aussi être utilisé pour vous assurer que les objets d'une classe sont toujours créés avec new .
[ Haut | Bas | Rechercher ]
Fred.h:
class Fred
{
public:
Fred();
// ...
private:
int i_;
static int j_;
};
Fred.cpp (ou Fred.C ou autre ):
Fred::Fred()
: i_(10) // OK: vous
pouvez (et vous devriez) initialiser les données membre de cette
façon
j_(42) // Error: vous ne pouvez pas initialiser une donnée
static comme ça.
{
//
...
}
// Vous devez définir les données static de
cette façon:
int Fred::j_
= 42;
[ Haut | Bas | Rechercher ]
// Fred.h
class Fred {
public:
// ...
private:
static int j_; //
Fred::j_ : donnée membre declaré static
// ...
};
Le générateur de lien vous grondera "Fred::j_ is not defined" (Fred::j_ n'est pas défini) à moins que vous ne définissiez (par opposition à déclariez) Fred::j_ dans (exactement) un de vos fichiers source :
// Fred.cpp
#include "Fred.h"
int Fred::j_ = quelque_expression_evaluant_un_int;
// Alternativement, si vous désirez utiliser la valeur par défaut
0 pour les ints static :
// int Fred::j_;
La place habituelle pour définir une donnée membre static de la classe Fred est dans le fichier Fred.cpp (ou Fred.C ou l'extension de fichier que vous utilisez).
[ Haut | Bas | Rechercher ]
Le fiasco de l'ordre d'initialisation des static est un moyen subtile et un aspect habituellement mal compris du C++. Malheureusement il est très difficile à détecter -- les erreurs se manifeste avant que le main() commence.
En bref, supposez que vous avez deux objets static x et y qui sont définis dans deux fichiers sources séparés, disons x.cpp et y.cpp. Supposez maintenant que le constructeur de l'objet y appelle une méthode de l'objet x.
Voilà. C'est aussi simple que ça.
La tragédie est que vous avez 50%-50% de chances de mourir. Si il arrive que l'unité de compilation correspondant à x.cpp soit initialisée avant celle correspondant à y.cpp, tout va bien. Mais si l'unité de compilation correspondant à y.cpp est initialisée d'abord, alors le constructeur de y sera en route avant le constructeur de x, et vous êtes cuît. C'est à dire que le constructeur de y appelera une méthode de l'objet x, alors que l'objet x n'a pas encore été construit.
Si vous pensez que c'est "excitant" de jouer à la roulette russe avec la moitié du barillet chargé, vous pouvez vous arrêter de lire ici. Si au contraire vous aimez augmenter vos chances de survie en prévenant les désastres de manière systématique, vous serez probablement intéressé par la prochaîne FAQ .
Note: Le fiasco de l'ordre d'initialisation des static ne s'applique pas au types de données prédefinis/intrinsèques comme int ou char*. Par exemple si vous créez un objet staticfloat, il n'y a jamais de problèmes avec l'ordre d'initializarion. Les seules fois où l'ordre d'initialisation statique est vraiment un fiasco est lorsque vos objets globaux ou static ont un constructeur.
[ Haut | Bas | Rechercher ]
Par exemple, supposez que vous ayez deux classes, Fred et Barney. Il y a un objet Fred global appelé x, et un objet Barney global appelé y. Le constructeur de Barney invoque la méthode goBowling() (va jouer au bowling) de l'objet x. Le fichier x.cpp définie l'objet x :
// Fichier x.cpp
#include
"Fred.hpp"
Fred x;
Le fichier y.cpp définie l'objet y:
// Fichier y.cpp
#include
"Barney.hpp"
Barney y;
Pour être complet, le constructeur de Barney pourraît ressembler à quelque chose comme :
// Fichier Barney.cpp
#include "Barney.hpp"
Barney::Barney()
{
// ...
x.goBowling();
// ...
}
Comme décrit ci-dessus , le désastre intervient si y est construit avant x, ce qui arrive 50% du temps puisqu'ils sont dans deux fichiers sources différents.
Il y a beaucoup de solutions à ce problème, mais une solution très simple et completement portable est de remplacer l'objet (de type Fred) global x, par une fonction globale x(), qui retourne par réference l'objet Fred.
// File x.cpp
#include "Fred.hpp"
Fred& x()
{
static Fred* ans = new
Fred();
return
*ans;
}
Puisque les objet locaux static sont construits la première fois (et seulement la première fois) que le flux de contrôle passe sur la déclaration, l'instruction ci-dessus new Fred() sera non seulement executée une fois : la première fois que x() est appelée, mais chaque appel suivant retournera le même objet de type Fred (celui pointé par ans). Tout ce qu'il reste à faire est de changer x en x():
// Fichier Barney.cpp
#include "Barney.hpp"
Barney::Barney()
{
// ...
x().goBowling();
// ...
}
Le nom est Construction à la première utilisation car cela fait exactement ce que ça dit : l'objet global Fred est construit à sa première utilisation.
Le défaut de cette approche est que l'objet Fred n'est jamais detruit. Le livre C++ FAQ contient une seconde technique qui solutionne ce souscis (mais au risque de générer un fiasco dans l'ordre de de-initialisation des variables statiques").
Notez que vous avez pas besoin de faire ça pour les types intrinsèques/prédefinis commeint ou char*. Par exemple si vous créez un staticfloat ou un float global, il n'y a pas besoin de l'emballer dans une fonction. La seule fois où l'initialisation statique peut vraiment tourner au fiasco est lorsque vos objets static ou globaux ont un constructeur.
[ Haut | Bas | Rechercher ]
Short answer: it's possible to use a static object rather than a static pointer, but doing so opens up another (equally subtle, equally nasty) problem.
Long answer: sometimes people worry about the fact that the previous solution "leaks." In many cases, this is not a problem, but it is a problem in some cases. Note: even though the object pointed to by ans in the previous FAQ is never deleted, the memory doesn't actually "leak" when the program exits since the operating system automatically reclaims all the memory in a program's heap when that program exits. In other words, the only time you'd need to worry about this is when the destructor for the Fred object performs some important action (such as writing something to a file) that must occur sometime while the program is exiting.
In those cases where the construct-on-first-use object (the Fred, in
this case) needs to eventually get destructed, you might consider changing
function
// File x.cpp #include "Fred.hpp" Fred& x() { static Fred ans; // was static Fred* ans = new Fred(); return ans; // was return *ans; }
However there is (or rather, may be) a rather subtle problem with this change. To understand this potential problem, let's remember why we're doing all this in the first place: we need to make 100% sure our static object (a) gets constructed prior to its first use and (b) doesn't get destructed until after its last use. Obviously it would be a disaster if any static object got used either before construction or after destruction. The message here is that you need to worry about two situations (static initialization and static deinitialization), not just one.
By changing the declaration from
The point is simple: if there are any other static objects whose destructors might use ans after ans is destructed, bang, you're dead. If the constructors of a, b and c use ans, you should normally be okay since the runtime system will, during static deinitialization, destruct ans after the last of those three objects is destructed. However if a and/or b and/or c fail to use ans in their constructors and/or if any code anywhere gets the address of ans and hands it to some other static object, all bets are off and you have to be very, very careful.
There is a third approach that handles both the static initialization and static deinitialization situations, but it has other non-trivial costs. I'm too lazy (and busy!) to write any more FAQs today so if you're interested in that third approach, you'll have to buy a book that describes that third approach in detail. The C++ FAQs book is one of those books, and it also gives the cost/benefit analysis to decide if/when that third approach should be used.
[ Haut | Bas | Rechercher ]
Supposez que vous avez une classe X possedant un objet staticFred :
// Fichier X.hpp
class X {
public:
// ...
private:
static Fred x_;
};
Naturellement ce membre static est initialisé séparement :
// Fichier X.cpp
#include "X.hpp"
Fred X::x_;
Naturellement aussi l'objet Fred sera utilisé dans une ou plusieurs des méthodes de X:
void X::someMethod()
{
x_.goBowling();
}
Mais maintenant le "scenario catastrophe" est que quelqu'un, quelque part, appelle de quelque manière cette méthode avant que l'objet Fred soit construit. Par exemple, si quelqu'un crée un objet static X et invoque la méthode someMethod() pendant l'initialisation static, alors vous êtes à la merci du compilateur : c'est à dire si le compilateur construira X::x_ avant ou après que someMethod() soit appelé. (Notez que le comité ANSI/ISO C++ travaille sur ce problème, mais que les compilateurs ne sont pas en général n'implantent pas ces changements; surveillez cet espace pour une mise-à-jour dans le futur.)
Dans tous les cas, c'est toujours portable et sûre de modifier le membre de donnée X::x_static en une fonction membre static:
// Fichier X.hpp
class X {
public:
// ...
private:
static Fred& x();
};
Naturellement ce membre static est initialisé séparement:
// Fichier X.cpp
#include "X.hpp"
Fred& X::x()
{
static Fred* ans = new
Fred();
return
*ans;
}
Il ne reste plus qu'a remplacer x_ par x():
void X::someMethod()
{
x().goBowling();
}
Si vous êtes super sensible à la performance de votre programme et que vous êtes souieux de délai introduit par un appel de fonction suplémentaire à chaque invoquation de X::someMethod() vous pouvez mettre un static Fred& à la place. Comme vous vous en souvenez, les variables locales static sont seulement initialisées une fois (la première fois que le flux de contrôle passe sur la déclaration), ceci appelera donc X::x() une fois seulement au premier appel de X::someMethod() :
void X::someMethod()
{
static Fred& x = X::x();
x.goBowling();
}
Notez que vous avez pas besoin de faire ça pour les types intrinsèques/prédefinis commeint ou char*. Par exemple si vous créez un staticfloat ou un float global, il n'y a pas besoin de l'emballer dans une fonction. La seule fois où l'initialisation statique peut vraiment tourner au fiasco est lorsque vos objets static ou globaux ont un constructeur.
[ Haut | Bas | Rechercher ]
Yes.
If you initialize your built-in/intrinsic type using a function call, the static initialization order fiasco is able to kill you just as bad as with user-defined/class types. For example, the following code shows the failure:
#include <iostream> int f(); // forward declaration int g(); // forward declaration int x = f(); int y = g(); int f() { std::cout << "using 'y' (which is " << y << ")\n"; return 3*y + 7; } int g() { std::cout << "initializing 'y'\n"; return 5; }
The output of this little program will show that it uses y before initializing it. The solution, as before, is the Construct On First Use Idiom:
#include <iostream> int f(); // forward declaration int g(); // forward declaration int& x() { static int ans = f(); return ans; } int& y() { static int ans = g(); return ans; } int f() { std::cout << "using 'y' (which is " << y() << ")\n"; return 3*y() + 7; } int g() { std::cout << "initializing 'y'\n"; return 5; }
Of course you might be able to simplify this by moving the initialization code for x and y into their respective functions:
#include <iostream> int& y(); // forward declaration int& x() { static int ans; static bool firstTime = true; if (firstTime) { firstTime = false; std::cout << "using 'y' (which is " << y() << ")\n"; ans = 3*y() + 7; } return ans; } int&y() { static int ans; static bool firstTime = true; if (firstTime) { firstTime = false; std::cout << "initializing 'y'\n"; ans = 5; } return ans; }
And, if you can get rid of the print statements you can further simplify these to something really simple:
int&y(); // forward declaration int&x() { static int ans = 3*y() + 7; return ans; } int& y() { static int ans = 5; return ans; }
Furthermore, since y is initialized using a constant expression, it no longer needs its wrapper function -- it can be a simple variable again.
[ Haut | Bas | Rechercher ]
Lancer (throw) une exception. Voir [17.1] pour les détails.
[ Haut | Bas | Rechercher ]
It's a fairly useful way to exploit method chaining .
The fundamental problem solved by the Named Parameter Idiom is that C++ only supports positional parameters. For example, a caller of a function isn't allowed to say, "Here's the value for formal parameter xyz, and this other thing is the value for formal parameter pqr." All you can do in C++ (and C and Java) is say, "Here's the first parameter, here's the second parameter, etc." The alternative, called named parameters and implemented in the language Ada, is especially useful if a function takes a large number of mostly default-able parameters.
Over the years people have cooked up lots of workarounds for the lack of named
parameters in C and C++. One of these involves burying the parameter values
in a string parameter then parsing this string at run-time. This is what's
done in the second parameter of
The idea, called the Named Parameter Idiom, is to change the function's
parameters to methods of a newly created class, where all these methods return
We'll work an example to make the previous paragraph easier to understand.
The example will be for the "open a file" concept. Let's say that concept logically requires a parameter for the file's name, and optionally allows parameters for whether the file should be opened read-only vs. read-write vs. write-only, whether or not the file should be created if it doesn't already exist, whether the writing location should be at the end ("append") or the beginning ("overwrite"), the block-size if the file is to be created, whether the I/O is buffered or non-buffered, the buffer-size, whether it is to be shared vs. exclusive access, and probably a few others. If we implemented this concept using a normal function with positional parameters, the caller code would be very difficult to read: there'd be as many as 8 positional parameters, and the caller would probably make a lot of mistakes. So instead we use the Named Parameter Idiom.
Before we go through the implementation, here's what the caller code might look like, assuming you are willing to accept all the function's default parameters:
File f = OpenFile("foo.txt");
That's the easy case. Now here's what it might look like if you want to change a bunch of the parameters.
File f = OpenFile("foo.txt"). readonly(). createIfNotExist(). appendWhenWriting(). blockSize(1024). unbuffered(). exclusiveAccess();
Notice how the "parameters", if it's fair to call them that, are in random order (they're not positional) and they all have names. So the programmer doesn't have to remember the order of the parameters, and the names are (hopefully) obvious.
So here's how to implement it: first we create a new class (OpenFile)
that houses all the parameter values as private data members. Then all the
methods (
class File; class OpenFile { public: OpenFile(const string& filename); // sets all the default values for each data member OpenFile& readonly(); // changes readonly_ to true OpenFile& createIfNotExist(); OpenFile& blockSize(unsigned nbytes); ... private: friend File; bool readonly_; // defaults to false [for example] ... unsigned blockSize_; // defaults to 4096 [for example] ... };
The only other thing to do is make the constructor for class File to take an OpenFile object:
class File { public: File(const OpenFile& params); // vacuums the actual params out of the OpenFile object ... };
Note that OpenFile declares File as its friend
, that way OpenFile
doesn't need a bunch of (otherwise useless)
Since each member function in the chain returns a reference, there is no copying of objects and the chain is highly efficient. Furthermore, if the various member functions are inline, the generated object code will probably be on par with C-style code that sets various members of a struct. Of course if the member functions are not inline, there may be a slight increase in code size and a slight decrease in performance (but only if the construction occurs on the critical path of a CPU-bound program; this is a can of worms I'll try to avoid opening; read the C++ FAQs book for a rather thorough discussion of the issues), so it may, in this case, be a tradeoff for making the code more reliable.
[ Haut | Bas | Rechercher ]
Ecrire à l'auteur,
au traducteur,
ou en savoir plus sur la traduction.
C++ FAQ Lite fr |
Table des matières |
Index |
A propos de l'auteur |
© |
Téléchargez votre propre copie ]
Dernière révision Sun Apr 13 23:54:12 PDT 2003