Constructeur de classe et création d'instance.

Bonjour à vous!

J’ai un problème, ou plutôt une incompréhension sur un code que je suis en train d’écrire. J’utilise une classe qui permet la lecture de données sur un capteur connecté via une liaison I2C. Cette classe possède un constructeur qui initialise les attributs de la classe, et une méthode init() qui “ouvre” l’I2C, et initialise le capteur.

Pour ce qui me tracasse: dans mon programme principal, je crée une instance de la classe. J’aimerai pouvoir faire en sorte qu’il suffise de créer cette instance pour qu’elle soit prête à utiliser, donc j’ai voulu faire appel à la méthode init() dans le constructeur. Mais, pour une raison que j’ignore, cela ne semble pas fonctionner, et en tout cas la liaison série que j’ouvre ensuite n’envoie rien.

Par contre, si je crée l’instance avec le constructeur vide (sans l’appel à la méthode init()), et que dans le setup() j’appelle init(), là tout ce passe comme prévu (code ci-dessous), et j’ai bien mes infos qui arrivent sur la liaison série.

Je suppose que cela a à voir avec les portées de variables, mais si vous pouviez m’éclairer sur la cause de ce dysfonctionnement qui n’en est peut-être pas un, ce serait fort charitable!

Le code .ino:

#include "VEML6070.h"

VEML6070 blue = VEML6070();

 void setup(){
 	Serial.begin(115200);
 	blue.init(VEML6070_SINGLE);
 }

 void loop(){
 	Serial.print("blue: ");
 	Serial.println(blue.read());

	delay(500);

 }

L’include de la classe:

#ifndef VEML6070_H
#define VEML6070_H

#include "Arduino.h"
#include "Wire.h"

//Address defines
//The data are read from two diffrent addresses on this sensor
#define VEML6070_ADDRESS		0x38
#define VEML6070_LSB			0x38
#define VEML6070_MSB			0x39

//sensor flags for sensor setup
#define VEML6070_DEFAULT		0x02
#define VEML6070_ACK			(1 << 5)
#define VEML6070_ACK_TH			(1 << 4)
#define VEML6070_IT				(1 << 2)
#define VEML6070_SD 			(1 << 0)

//Used to simplify implementation of different intergration time from user program
typedef enum VEML6070_integration{
	VEML6070_HALF,
	VEML6070_SINGLE,
	VEML6070_DOUBLE,
	VEML6070_QUADRUPLE,
} integration_time;

class VEML6070{

public:

	VEML6070();
	~VEML6070();

	void init(byte _sensivity = VEML6070_SINGLE);

	void setSensivity(byte);
	void setAck(bool);
	void setAckTh(bool);
	void setShutdown(bool);

	unsigned int read();
	unsigned int lastReading() const;

protected:

	void _set(bool, bool, byte, bool) const;

private:
	unsigned int value;

	byte sensivity;
	bool ack;
	bool ack_th;
	bool shutdown;

};

et le code source de la classe:

#include "VEML6070.h"

//Defaut constructor of the class
VEML6070::VEML6070():sensivity(1), ack(false), ack_th(false), shutdown(false), value(0)
{
	//Set the default sensivity. The datasheet advise to run first at 1, and change if needed
}

//class destructor
VEML6070::~VEML6070(){
	setShutdown(true);
}

//Constructor with sensivity passed as arg.
void VEML6070::init(byte _sensivity){

	//copy sensivity set in function call
	sensivity = _sensivity;

	//Open wire
	Wire.begin();
	//Send init command
	_set(ack, ack_th, sensivity, shutdown);

	//wait a bit for the sensor to init
	delay(200);
}

//send a init command
void VEML6070::_set(bool _ack, bool _ack_th, byte _sens, bool _shutdown) const{
	byte init_byte = VEML6070_DEFAULT;

	init_byte |= _ack && VEML6070_ACK;
	init_byte |= _ack_th && VEML6070_ACK_TH;
	init_byte |= _sens << VEML6070_IT;
	init_byte |= _shutdown && VEML6070_SD;

	Wire.beginTransmission(VEML6070_ADDRESS);
	Wire.write(init_byte);
	Wire.endTransmission();


}

//Convenience function to change only sensivity
void VEML6070::setSensivity(byte value){
	_set(ack, ack_th, value, shutdown);
	sensivity = value;
}

//Convenience function to change only Ack
void VEML6070::setAck(bool value){
	_set(value, ack_th, sensivity, shutdown);
	ack = value;
}

//Convenience function to change only Ack threshold
void VEML6070::setAckTh(bool value){
	_set(ack, value, sensivity, shutdown);
	ack_th = value;
}

//Convenience function to change only shutdown
void VEML6070::setShutdown(bool value){
	_set(ack, ack_th, sensivity, value);
	shutdown = value;
}

//Read value from the sensor
unsigned int VEML6070::read(){

	Wire.requestFrom(VEML6070_MSB, 1);

	value = Wire.read();
	value = value << 8;

	Wire.requestFrom(VEML6070_LSB, 1);
	value |= Wire.read();

	return value;
}

//Get the last value read
unsigned int VEML6070::lastReading() const{
	return value;
}

Et maintenant, le code qui ne fonctionne pas. J’ai enlevé tout ce qui était identique aux trois fichiers ci-dessus.
.ino:

VEML6070 blue = VEML6070();

 void setup(){
 	Serial.begin(115200);
 }

Constructeur de la classe:

//Defaut constructor of the class
VEML6070::VEML6070():sensivity(1), ack(false), ack_th(false), shutdown(false), value(0)
{
	init();
}

Merci par avance pour vos lumières!

Bonjour,

Je ne sais pas si c’est ça le problème, mais dans le 1er cas l’init est fait après le Serial.begin() et dans le 2ème cas il est fait avant.

C'est juste. Je voulais tester la différence, je viens de le faire. En gardant l'appel à init() dans setup() mais en le plaçant avant Serial.begin(), ça ne change rien: ça marche aussi.

Et si je déplace le constructeur dans le setup(), et que le constructeur appelle la méthode init(), alors ça fonctionne aussi très bien, sans que j'ai besoin de l'appeler manuellement. Par contre, bien entendu, la portée de l'instance est limitée au setup().

Je pense donc que c'est bien une histoire de portée, mais ça m'échappe complètement! Je vais voir si je peux trouver des infos, mais jusque là mes recherches n'ont rien données.

Bonjour

Je ne sais pas si cela explique exactement tes symptômes, mais en général il faut éviter d'appeler des fonctions liées au hardware dans les constructeurs.

Le "vrai" source fourni au compilo est masqué par l'IDE arduino.
Je ne l'ai plus sous la main, mais il ressemble à

int main(){
  init();
  setup();
  while(1) loop();
}

La fonction init() est fournie par l'IDE arduino de manière non visible. Elle contient des initialisations hardware.

Lorsque tu crées une instance de classe en variable globale, le code associé au constructeur est exécuté avant même d'entrer dans le main(), donc avant l'init() arduino.
Sur les accès hardware, cette inversion de la séquence normale d'exécution peut avoir des effets de bord.

Le mieux est de produire des classes "dans la philosophie" de l'IDE arduino :

  • avec un constructeur qui se borne à initialiser les propriétés internes (RAM)
  • avec une méthode d'initialisation du hardware appelée explicitement depuis le setup()

Et si vraiment cela t'embête d'avoir un constructeur en appel explicite, tu dois pouvoir remplacer ton objet par une variable globale de type pointeur, et faire un NEW dans le setup().

tu peux aussi te faire un objet de type singleton (cf design patterns), qui ne fait que masquer ce que bricoleau te propose.
ceci dit, le désavantage sur arduino quand tu fais de l'allocation dynamique c'est qu'au moment de la compilation + téléversement tu ne vois pas la taille que prend vraiment ton programme en mémoire...

Merci Bricoleau pour ces explications, elle font sens. Je soupçonnais quelque chose de cet ordre-là, notamment parce que la classe Serial ne semblait pas fonctionner, je pensais donc qui se passait quelque chose sur lequel je n'ai pas le contrôle, et qui bloquait le déroulement normal des opérations. Mais il y a de fortes chances que ce soit en effet ça la raison, et d'ailleurs maintenant que j'y pense les bibliothèques Arduino ont souvent ce mécanisme: un créateur et une fonction d'initialisation, alors qu'en C++ sauf raison particulière il me semble que le créateur réalise, autant que possible l'initialisation.

J'ai pensé à utiliser un pointeur pour faire l'initialisation en une fois, mais dans le cas de cette classe ça ne fait pas vraiment sens, puisqu'elle n'est implantée qu'une seule fois dans le programme. Elle a simplement vocation à clarifier le code et à le rendre modulaire.

Zorro_X, ce que je connais des singleton ne me permet pas de bien comprendre ce que tu me propose, mais je vais prendre le temps d'y jeter un coup d'œil demain. Je sais que l'allocation dynamique peut avoir cet inconvénient-là. Ici le programme devrait être assez minime, mais c'est vrai que quand j'ai besoin de faire des allocation dynamiques je me demande souvent si ça passe ou non. D'autant que je manque de culture pour savoir exactement quelle place peut prendre une variable ou une classe allouée dynamiquement en mémoire...

Merci à vous!

Voici une explication du singleton en C++.
C'est une façon "élégante" et standardisée de masquer l'allocation dynamique que te propose bricoleau tout en ayant une portée globale de ton instance dans tout ton code comme tu en as besoin et comme ca peut être souvent le cas.

Merci pour le lien!
J'avoue ne pas y avoir compris grand chose, mais j'en ai cherché d'autres et celui-là m'a permis de bien saisir pourquoi on créait un singleton, comment on le créait, et ce qu'il apporte.

En fait j'étais complètement à coté dans ce que je supposais hier en répondant. J'avais compris que d'une manière générale en informatique et en programmation ça définissait un objet dont il n'existe qu'une instance, mais j'ignorais complètement les mécanismes qu'il pouvait y avoir derrière, notamment pour sécuriser son implémentation unique.

troisiemetype:
J'avoue ne pas y avoir compris grand chose, mais j'en ai cherché d'autres et celui-là m'a permis de bien saisir pourquoi on créait un singleton, comment on le créait, et ce qu'il apporte.

L'essentiel c'est que tu aies compris !
Si la programmation objets te plaît et que les design patterns te semblent intéressants, je peux te conseiller ce livre Design Patterns - Tête la première. Il est très bien expliqué pour les débutants avec des exemples & exercices très parlants.

Après, il faut faire extrêmement attention avec les design patterns : il ne faut pas que cela devienne une "religion" mais que cela reste ce que c'est : une source d'inspiration. Sinon tu vas finir par pondre des "hello word" qui font 3000 lignes de code... :wink:

Oui, je sais que la programmation, c'est avant tout une affaire de bon sens! Et souvent, bie nque ce soit sur des problèmes simples, ce genre de question survient: comment faire ceci ou cela, cela vaut-il la peine de faire une classe, s'il y en a plusieurs, quelle structure adopter pour que ce soit adapté à ce qu'elle doit faire, et en même temps que l'ensemble soit homogène, etc.

Je pense que je reviendrai aux design paterns. J'avais survolé rapidement la question dans un livre sur la programmation en python, mais comme certaines manières de faire répondent à des problématiques que je n'ai pas encore rencontré, ou qui me dépassent, j'ai simplement rangé dans un coin de mon crâne que ce sujet vaudra le coup que j'y revienne le jour où j'ai un blocage. :wink:

Et je me mets le livre de coté, je vais y jeter un coup d'œil tout de suite.

troisiemetype:
je vais y jeter un coup d'œil tout de suite.

bonne lecture ! :wink: