Overblog Suivre ce blog
Administration Créer mon blog
2 mars 2016 3 02 /03 /mars /2016 19:14

Chatting with a friend one of the last few days, I realized that Dub is an unknown tool, whereas it is certainly one of the best arguments in favor of D these days. I had to repair this injustice.


Introducing Dub

Dub, the D package manager is a masterpiece in D tooling world. It is the D counterpart to tools like Cargo in Rust, Pip in Python or CPAN in Perl.

Dub is written by Sönke Ludwig, the author of Vibe.d. It can be downloaded on http://code.dlang.org. There are different packages for different Linux distros, as well as precompiled binaries for Windows and OSX.

Dub replaces make and integrates with IDEs such as MonoDevelop's Mono-D plugin. It can also generate Visual D projects if you want to use Visual studio build system on Windows.

Creating a project

Let's suppose we want to create an awesome TodoList project. Our project will use sqlite to store lists. Let's create a project

$ mkdir todolist
$ cd todolist
$ dub init

The dub init command creates an empty project file named dub.sdl and a source directory containing an app.d source file:

.
        ├── dub.sdl
        └── source
            └── app.d

The source directory will contain the source code of your project. The app.d file is the file containing the main() function. For now, it is barely more thar a hello world file.

The dub.sdl file contains the project description in the SDLang (http://sdlang.org) format. This file could also be written in JSON in you prefer the JSON format. For now, its content is minimal:

name "todolist"
description "A minimal D application."
copyright "Copyright © 2016, olivier"
authors "olivier"

Dub homepage also contains a list of packages. By browsing them, you discover the d2sqlite3 package which is a binding to the sqlite C API. Let's tell Dub you want to use it in your project by adding the following line at the end of the dub.sdl file.

dependency "d2sqlite3" version=">=0.9.7"

Version 0.9.7 is the latest package version available at the time I write this post. The greater than sign before it tells Dub I want to be able to upgrade it to newer versions when they will be released. Let's try to build and run our project:

$ dub
Fetching d2sqlite3 0.9.7 (getting selected version)...
Placing d2sqlite3 0.9.7 to /home/olivier/.dub/packages/...
Performing "debug" build using dmd for x86_64.
d2sqlite3 0.9.7: building configuration "with-lib"...
todolist ~master: building configuration "application"...
Linking...
Running ./todolist
Edit source/app.d to start your project.

Let's explain what has just happened. Dub has downloaded the application dependency, the d2sqlite3 package. This package was put into a local dub registry and compiled. If this package also had other dependencies, they would had been fetched and compiled too. Fortunately, for the sake of our basic example, I selected package with no dependency. The application
was compiled with the DMD compiler in debug configuration. The last line shows the output of our program, telling us to edit source/app.d to start our project.

Editing the code

Let's listen to what dub says and edit the app.d file. First I will import the d2sqlite3 module.

import d2sqlite3;

Dub takes care of passing the path to the modules it downloaded to the compiler for us, so we don't have to bother rememebering where they are (they are put in my /home/olivier/.dub/packages directory). Let's create our database file.

private void createDatabase(string filename)
{
    auto db = Database(filename);
    db.begin();
    scope (success) db.commit();
    scope (failure) db.rollback();

    // create our tables
    createListTable(db);
    createEntryTable(db);
}

This code makes use of D's transactional features (AKA scope statement) to make sure the database is either committed or rolled back, but never half-baked. Let's now create our tables:

private void createListTable(ref Database db)
{
    db.execute(
        "CREATE TABLE LIST(
            LIST_ID INTEGER PRIMARY KEY AUTOINCREMENT,
            LIST_NAME TEXT UNIQUE
        )");
}

private void createEntryTable(ref Database db)
{
    db.execute(
        "CREATE TABLE ENTRY(
            ENTRY_ID INTEGER PRIMARY KEY AUTOINCREMENT,
            ENTRY_POSITION INTEGER,
            ENTRY_VALUE TEXT,
            ENTRY_LIST_ID INTEGER,
            FOREIGN KEY (ENTRY_LIST_ID) REFERENCES LIST(LIST_ID)
        )");
}

Again, nothing really complicated here, this is just plain SQL, as I don't want to distract you by introducing complex database bindings or ORMs. This code should be pretty straightfoward. A list has a name and contains entries which have a position in the list and a textual value. Entries store a reference to the list they belong to as a foreign key.

Let's call the createDatabase function from main:

import std.file;

void main()
{
    if (!exists("todo.db"))
        createDatabase("todo.db");
}

Save it and compile/run it again.

$ dub
Performing "debug" build using dmd for x86_64.
d2sqlite3 0.9.7: target for configuration "with-lib" is up to date.
todolist ~master: building configuration "application"...
Linking...
To force a rebuild of up-to-date targets, run again with --force.
Running ./todolist

Dub tells me that the sqlite package is still up to date (fortunately!), then rebuilds and run the application. I now have a 5 kB todo.db file in my directory containing my database, meaning that my code did work:

.
        ├── dub.sdl
        ├── dub.selections.json
        ├── source
        │   └── app.d
        ├── todo.db
        └── todolist

This is the end of our small example. Its purpose was simply to show how easy it is to integrate packages with dub and start coding. I leave to you the complete implementation of the TodoList application if you are interested in this.

Publishing packages

Publishing a Dub package is fairly easy. You have to publish your project on GitHub or BitBucket and to register it into Dub (you need a Dub account, which is free). Then anyone who adds the name of your project as a dependency in her/her dub.sdl project file will automatically have the content of your git repository available to his/her project. Whenever you want to release a new version, you'll simply have to create a git tag in your repository, and your users will be warned about a new release the next time they recompile their project.

At the time of this writing, the Dub package index contains about 700 packages.

Conclusion

I hope this post did interest you enough to go and check out the Dub documentation available at http://code.dlang.org. You'll learn a ton of things I did not cover in this post, such as creating libraries or running unit test builds.

Dub makes D development a lot easier by managing dependencies and letting the developer focus on the important thing: the code. It is a really nice tool that every D coder should be aware of.

Repost 0
Published by Olivier - dans Programmation
commenter cet article
26 mai 2012 6 26 /05 /mai /2012 13:55

Here is a common first year exercice question :

 

Write a function returning a random integer between 0 and n, n being passed as an argument to the function.

 

And here a common answer in C : 

 

 #include <stdlib.h>

// NB. don't forget to initialize with srand()

int random_integer(int n)
{
return (rand() / (RAND_MAX + 1.0) * (n+1));
}

This solution avoids the modulo operation on the value returned by rand() since it is likely to produce a non-uniform number distribution if RAND_MAX is not a multiple of n.

 

Anyway, the shiny new C++11 standard now comes with increased random number facilities located in the random header. Here is how to use them to produced the same result with them :

 

 #include <random> // clearer than stdlib, isn't it ? 

// NB. don't forget to initialize generator with its seed() method

int random_integer(std::mt19937& generator, int n)
{
std::uniform_int_distribution<int> dist_n(0, n);
return dist_n(generator);
}

Some remarks about this design :

  • C++11 standard provides several random number engines which are clearly identified by their names and their fomulas. Users can choose between them depending on their needs instead of trusting the rand() black box. In this example, I used the Mersenne Twister algorithm.
  • There is no shared global engine state like in C. With this design, each thread can have its own random number engine, which is better suited for multicore programming.
  • RNG engines are separated from distributions, which control the probability of a value to be a given range. Notable supported distributions are uniform (used in this article), normal (Gaussian), Poisson, etc.
  • Readability is way improved. Simply reading the dist_n variable declaration is far more understandable than the C equivalent formula.
Repost 0
Published by Olivier - dans Programmation
commenter cet article
21 mai 2012 1 21 /05 /mai /2012 18:25

Hi everyone, it's been a while !

 

One of the things that I apreciate in D is the way comparison operators overloading is implemented. Instead of having six individual operators to overload (<, <=, ==, !=, >= and >), you just have to implement a single special method called opCmp.

 

This opCmp method behaves exactly as memcmp or strcmp do in C :

  • It must return lesser than 0 if the first operand is lesser than the second.
  • It must return 0 for equality
  • It must return greater than 0 if the first operand is greater than the second.

While reading about template programming tricks, I stumbled across something called the Barton-Nackman trick, which is a special use of the Curiously Recurring Template Pattern.

 

So here is a simple trick to enable the overloading of the six comparison operators in C++ in one member function.

 

First of all, define a template class which has the overloads of the six operators which basically just interpret the return value of the opCmp method of its template parameter.

 

 template <class T>
class comparable
{
friend bool operator<(const T& t1, const T& t2)
{
return t1.opCmp(t2) < 0;
}
friend bool operator<=(const T& t1, const T& t2)
{
return t1.opCmp(t2) <= 0;
}
friend bool operator==(const T& t1, const T& t2)
{
return t1.opCmp(t2) == 0;
}
friend bool operator!=(const T& t1, const T& t2)
{
return t1.opCmp(t2) != 0;
}
friend bool operator>=(const T& t1, const T& t2)
{
return t1.opCmp(t2) >= 0;
}
friend bool operator>(const T& t1, const T& t2)
{
return t1.opCmp(t2) > 0;
}
};



Now, when you will need to implement all these operators at one for one of your types, you will just have to inherit privately from this class by using the CRTP and define an opCmp method in you class. Here is a simple example of such a thing :

 

 class number : private comparable<number>
{
int m_value;
public:
number(int n) : m_value(n)
{
}
int opCmp(const number& n) const
{
return m_value - n.m_value;
}
};

 

As you can see, only one method is now needed to implement all the 6 operators. Private inheritance is sufficient in this case and you be read is-implemented-using-a instead of the classical is-a relationship that is introduced by public inheritance. This is a good thing in terms of conception as it reduced dramatically the coupling and the dependencies between these two classes.

 

You'll find below a basic unit test of this number class :

 

 int main()
{
number three(3);
number three_again(3);
number two(2);
number five(5);
assert(three <= three_again);
assert(three == three_again);
assert(three >= three_again);
assert(two < three);
assert(two <= three);
assert(three > two);
assert(three >= two);
assert(three != two);
assert(two != three);
return EXIT_SUCCESS;
}
 
Repost 0
Published by Olivier - dans Programmation
commenter cet article
1 mai 2011 7 01 /05 /mai /2011 12:17

 

 

Hi, I am going to review the facilities provided by most object-oriented languages to add some methods to a type without subclassing nor accessing its inner implementation details. You can think of it as alternatives to the Adapter design pattern.

Let's say I have a type T and want to add a method to this type. For demonstration purposes, I will add a method called “universe” that will output the integer 42, which is, as Douglas Adams fans may know, the answer to life, universe and everything. Of course, in a real life program, one will add some more useful methods.

C#

 

C# provides something called “Extension methods”. These are simple public static methods in public static classes that take a first parameter with the this keyword.

 

 class T
{

// implementation of T...
}
public
static class Ext
{

public static int universe(this T t)
{
return 42;
}
}

 

Then one may call the universe method as if it was part of the T class :

 T t = new T();
int
i = t.universe();

One could notice that we get rid of any mention of the Ext class. It is only here because of the dotnet limitation that forbids global functions. Every method must belong to a class.

 

D

 

D provides a simple yet powerful mechanism called Uniform Function Call Syntax (UFCS). Basically, any function f(a[, ...]) can be rewritten as a.f([...]).

 

 class T
{
// implementation of T
}

int
universe(in T t) pure nothrow
{
return 42;
}

T t = new T;
t.universe();

 

This is much simpler than C# Extension methods and more powerful as it does not only applies to classes or structs, but also to built in types. Andrei Alexandrescu used them on built-in arrays to get them the Range interface he had defined for containers (see std.array and std.range D modules for more information).

Objective-C

 

Objective-C provides a way to add methods at runtime to an existing class without subclassing. This is called “categories”.

 

 @interface T : NSObject
{
// T fields
}
// T methods
@end
@implementation
T
// implementation of T methods
@end

// Add a new Category of T with a universe method
@interface
T (Universal)
- (int) universe;
@end

@implementation
T(Universal)
- (int) universe
{
return 42;
}
@end


// all the instances of T in the process now have a universe method
T* t = [[ T alloc] init];
int i = [ t universe ];

Python

 

Python being dynamic, it is very simple to add methods and fields to a class using the built in setattr function :

 

 class T:
pass

def
addUniverseToAClass(aClass):
def func(self):
return 42
setattr(aClass, "universe", func)

t = T()
addUniverseToAClass(T)
i = t.universe()

Java

  System.err.println(“I'm afraid I can't let you do that, Dave.”); // HA-HA ! 

 

Purpose

 

At this point, some of you may ask what is the point of doing all this ? These techniques can be very useful when doing some generic programming. Suppose you have written a very nice generic algorithm that works well with your types. And then someone provides you a new class that has similar abilities but a slightly different interface. Why would you subclass this new type ?

Inheriting a class creates a strong bind between a base class and its offspring that can later have consequences on the evolution of your code base. As you may have noticed, except Python, none of the programming languages presented here supports multiple inheritance...

Repost 0
Published by Olivier - dans Programmation
commenter cet article
17 janvier 2011 1 17 /01 /janvier /2011 13:53

 

 

Depuis la version 2.0 (Octobre 2000), le langage Python supporte les compréhensions de liste. Ce nom cache une syntaxe simple et concise pour créer de nouvelles listes à partir d'autres (en fait, tout objet itérable). C'est une fonctionnalité héritée des langages fonctionnels, ce qui explique que beaucoup de débutants venant de langages impératifs n'est pas le réflexe de les utiliser.

 

Cas d'exemple

 

Nous avons une liste d'entiers (peut importe son origine). Nous souhaitons filtrer cette liste et ne garder que les entiers dont le carré est supérieur à 50. En style impératif, on écrirait :

 nouvelleListe = list()
for entier in ancienneListe:
if entier ** 2 > 50:
nouvelleListe.append(entier)

 

Ces quatre lignes peuvent être simplifiées pour n'en faire qu'une seule au moyen d'une compréhension de liste :

 

 nouvelleListe = [ entier for entier in ancienneListe if entier ** 2 > 50 ]

 

Une compréhension de liste procède par filtrage pour créer une liste à partir d'une autre. La syntaxe est la suivante :

 

[ élémentÀAjouter for élément in séquence {if expression} ]

 

La première chose à remarquer est que l'expression créant une liste, elle est entourée de crochets [ et ], qui délimitent une liste littérale. Une compréhension de liste est divisée en trois parties. La première (ici appelée « élémentÀAjouter ») est simplement une expression qui sera ajoutée à la liste à chaque itération. La deuxième partie est identique à une boucle for classique. Pas de surprises ici. La troisième partie est optionnelle et permet de filtrer certains éléments. Dans le précédent exemple, on n'a retenu que les éléments dont le carré était supérieur à 50.

 

Intérêt des compréhension de listes

 

Outre leur concision, les compréhensions de listes améliorent la lisibilité d'un programme. Un coup d'oeil suffit à en reconnaître une et l'on sait immédiatement ce que l'on va faire. C'est un avantage indéniable pour un langage dynamique. L'exemple itératif demande de lire toute la boucle pour comprendre ce qu'elle fait. En outre, une compréhension de liste est beaucoup plus simple à appréhender que :

 

 nouvelleListe = list(filter(lambda x: x ** 2 > 50, ancienneListe)) 

 

Qui est complètement abscons pour beaucoup de développeurs alors qu'il fait la même chose.

 

Repost 0
Published by Olivier - dans Programmation
commenter cet article
8 novembre 2010 1 08 /11 /novembre /2010 10:59

« Le compte est bon » est un célèbre jeu télévisé, d'abord autonome, puis intégré à l'émission « Des chiffres et de lettres ». Le principe est simple, on tire au hasard un nombre entre 101 et 999. Deux candidats devront aboutir à ce résultat d'après six nombres tirés au hasard parmi la liste suivante : 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 25, 50, 75 et 100 en utilisant les quatre opérations arithmétiques de base. Nous allons voir ensemble comment réaliser une application permettant de trouver une solution à ce jeu en langage Python.

 

Données utilisables

Chaque opération possible (addition, multiplication, soustraction et division) est une opération binaire. C'est-à-dire qu'elle fait intervenir deux opérandes. À partir de notre liste de 6 entiers, on va former des paires sur lesquelles on effectuera les opérations. On n'a le droit d'utiliser chaque nombre qu'une fois, mais on peut réutiliser le résultat d'une opération précédente. Donc à chaque fois qu'on fait une opération, on « perd » les deux opérandes, et on « gagne » le résultat de l'opération.

 

On voit ainsi se dessiner l'esquisse d'un algorithme récursif de type backtracking.

 

Capture d’écran 2010-11-08 à 11.04.19

Réduisons l'espace de recherche

Prenons deux nombres A et B au hasard. On peut remarquer plusieurs choses :

  • L'addition est commutative. C'est-à-dire que A + B et B +A donneront toujours le même résultat. Il en est de même pour la multiplication. Il est donc inutile de faire la deuxième opération si on a déjà fait la première.

  • Si A = B, la soustraction donnera 0. Faire rentrer 0 dans nos données donnera des calculs ultérieurs inutiles ou interdits. On peut donc éviter ce calcul :

    • A + 0 = A (inutile)

    • A x 0 = 0 (inutile)

    • A : 0 est interdit

  • Si A < B, la soustraction donnera un entier négatif (on sort de l'espace de solutions) et la division entière donnera 0. Ce sont donc également des calculs inutiles.

 

Ces constatations nous amènent à ne faire que les opérations pour A >= B, et à éviter la soustraction si A = B. On réduit donc de plus de moitié notre espace de recherche, gagnant ainsi un temps précieux !

 

Générateur de paires d'entiers

Nos quatre opérations étant binaires, nous devons, à partir d'une liste d'entiers, générer toutes les paires d'opérandes possibles. Cela est facilement réalisable en utilisant une liste triée par ordre décroissant. Il suffit de maintenir deux index sur cette liste. On déplace un index à chaque accès et un autre chaque fois que le premier sort des limites de la liste. On s'arrête lorsque l'on ne peut plus rien déplacer.

 

Journal des opérations

Afin de pouvoir afficher les étapes du calcul lorsque l'on a trouvé une solution, on utilise une liste de chaînes de caractères. Après chaque opération, on note celle-ci dans la liste. Si un nœud de l'arbre d'exploration n'a donné aucun résultat, on l'enlève de la liste.

 

Code source de notre programme

 #!/usr/bin/python



import sys



# Les quatre fonctions arithmetiques de base

add = lambda x, y: x + y

sub = lambda x, y: x - y

mul = lambda x, y: x * y

div = lambda x, y: x // y



class Operation:

"""

Classe associant une operation a sa description textuelle, pour pouvoir

afficher le detail des calculs effectues lorsqu'on a trouve le resultat.

"""

def __init__(self, func, desc):

"""

Constructeur : initialise les deux champs

func : pointeur vers la function qui fait le calcul

desc : chaine de caractere, descriptif

"""

self.func = func

self.desc = desc



# Les quatre operations

ALL_OPERATIONS = (Operation(add, "+"), Operation(sub, "-"),

Operation(mul, "x"), Operation(div, ":"))



# Les operations qui ne peuvent pas donner 0 comme resultat

OPERATIONS = (Operation(add, "+"), Operation(mul, "x"),

Operation(div, ":"))



#----------------------------------------------------------------------------



class PairIterator:

"""

Classe donnant une suite de paire de nombres a partir d'une liste de

nombres.



"""

def __init__(self, nums):

assert len(nums) >= 2, "La liste doit contenir au moins 2 elements"

self.nums = nums

self.index1 = 0

self.index2 = 1



def __iter__(self):

""" La classe est son propre iterateur. """

return self



def __next__(self):

""" Renvoie la prochaine paire d'entiers """

# Si on est arrive au bout de la liste de paires

if self.index2 == len(self.nums):

raise StopIteration

# Cree une paire

p = (self.nums[self.index1], self.nums[self.index2])



# Avance la position pour la prochaine fois

if self.index2 < len(self.nums) - 1:

self.index2 += 1

else:

self.index1 += 1

self.index2 = self.index1 + 1



return p



def next(self):

"""

Meme fonction que la precedente, par souci de compatibilite

avec python 2.x

"""

return self.__next__()

#----------------------------------------------------------------------------



def algo(entiers, aTrouver, log=[]):

"""

Fonction de recherche de solutions.



Parametres:

entiers: liste de donnees utilisables (entiers)

aTrouver: resultat auquel aboutir (entier)

log: liste des operations deja effectuees (liste de chaines)

"""

assert len(entiers) >= 2, "La liste doit contenir au moins 2 elements"



# Pour chaque paire d'entiers

paires = PairIterator(entiers)

for paire in paires:

# On evite de faire des calculs qui donnent 0 comme resultat

ops = OPERATIONS if paire[0] == paire[1] else ALL_OPERATIONS

for op in ops:

# On fait le calcul

resultat = op.func(paire[0], paire[1])

# Note l'operation dans le journal

log.append("%d %s %d = %d" % (paire[0], op.desc, paire[1],

resultat))



# Si on a trouve la solution

if resultat == aTrouver:

# On affiche les operations effectuees et on quitte

for ligne in log:

print(ligne)

sys.exit(0)

else:

# On prend les autres nombres et le resultat de l'operation

entiers2 = [n for n in entiers if n not in paire]

entiers2.append(resultat)

# et on recommence

entiers2.sort(reverse = True)

if len(entiers2) >= 2:

algo(entiers2, aTrouver, log)

# L'operation n'a pas donne de resultat, on l'enleve du journal

log.pop()



#----------------------------------------------------------------------------



VALIDES = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 25, 50, 75, 100)



if len(sys.argv) != 8:

print("Pas assez d'arguments.")

print("usage : compte nb1 nb2 nb3 nb4 nb5 nb6 aTrouver")

sys.exit(1)

else:

try:

entiers = [ int(i) for i in sys.argv[1:7] ]

aTrouver = int(sys.argv[-1])

except:

print("Les arguments de ce programmes doivent etre des entiers")

sys.exit(2)



# Verifie que le nombre a trouver est valide

if aTrouver <= 100 or aTrouver >=1000:

print("Argument invalide")

print("Le nombre a trouver doit etre compris entre 101 et 999")

sys.exit(3)



# Verifie que tous les nombres sont dans la liste des valides

if any(e not in VALIDES for e in entiers):

print("Argument(s) invalide(s)")

print(("Les nombres doivent etre dans la liste %s" % (str(VALIDES))))

sys.exit(3)



entiers.sort(reverse = True)

algo(entiers, aTrouver)

print ("Aucun resultat.")

Repost 0
Published by Olivier - dans Programmation
commenter cet article
16 octobre 2010 6 16 /10 /octobre /2010 14:06

 

J'ai brièvement expliqué dans mon précédent billet l'intérêt des rvalue references de C++11 et nous avons pu voir que les améliorations de performances dans les programmes génériques justifiaient amplement leur inclusion dans la nouvelle norme. Néanmoins, je tiens à rappeler que cet ajout se fera aux dépens d'une complexité accrue du langage. Le programmeur débutant devra maitriser les concepts de déplacement et de copie, ainsi que les temporaires anonymes, avant de pouvoir tirer avantage de cette fonctionnalité.

Le langage D se voulant être un C++ plus simple, il se devait de résoudre ce problème de manière plus élégante.

Classes et structures

 

Premier constat, une classe D n'a pas grand-chose en commun avec son homologue C++. D a choisi la même approche que C#, en séparant clairement les structures et les classes, alors que ces deux types correspondent plus ou moins à la même chose en C++.

En D, une classe est toujours instanciée sur le tas, au moyen de l'opérateur new et est toujours manipulée par référence. De cette manière, aucune copie accidentelle d'objet classe n'aura lieu tant que le programmeur l'aura expressément demandé (appel à une méthode clone, par exemple). Il n'est pas possible de surcharger l'opérateur d'affectation pour les classes.

 

Mais D propose aussi un type structure, qui est proche des structures/classes C++ et se manipule aussi par valeur ; avec quelques différences cependant. Les structures D ont notamment une contrainte supplémentaire : elles doivent être déplaçables. C'est-à-dire que l'état de l'objet doit être indépendant de sa position en mémoire. En pratique, cela signifie qu'une structure ne peut contenir de pointeur vers ses propres membres. Le code suivant est donc incorrect en D :

 struct S
{
private:
int i;
int* pi;

public:
this(int value)
{
i = value;
pi = &i;
}
}

 

En pratique, cela ne pose pas de problème, car cette technique n'est jamais indispensable. Le fait d'avoir des objets déplaçables présente des avantages d'optimisation pour le compilateur ou l'environnement d'exécution, comme le ramasse-miettes.

Le constructeur postblit

 

Contrairement au langage C++, le langage D ne propose pas de constructeur de copie pour les structures. Le constructeur de copie est un mécanisme du C++ dans lequel une méthode spécifique est appelée lorsqu'un objet doit être copié. Si le programmeur ne définit par de constructeur de copie, le compilateur C++ en générera un automatiquement. Le constructeur de copie généré se bornera à faire une copie brute (memcpy) des données de l'objet. Cela peut suffire pour les types simples (appelés POD, pour Plain Old Data), mais est clairement insuffisant pour un objet complexe, possédant des pointeurs vers des données externes. C'est précisément la raison pour laquelle C++ permet au programmeur d'écrire son propre constructeur de copie.

 

Comme je l'ai dit, le langage D ne propose pas de constructeur de copie. En D, toutes les copies de structures seront toujours assurées par le langage, qui fera toujours un memcpy des données de la structure. Comme cette technique est insuffisante pour les types complexes, D propose un autre mécanisme appelé le constructeur postblit. C'est en fait une méthode optionnelle qui sera appelée après une copie de structure. J'insiste bien sur le fait que ce n'est pas un constructeur de copie car il ne remplace pas le mécanisme de copie de la structure, il est appelé après la copie brute des champs de la structure.

Dans un constructeur postblit, le programmeur insèrera généralement du code qui dupliquera les objets manipulés par référence. On a donc une copie en deux étapes :

  • Le langage copie la source via memcpy, la copie possède des champs qui pointent vers les mêmes données que la source.

  • Le constructeur postblit duplique les données références.

 struct T
{
private:
int[] pi;
public:
/// Constructeur
this(size_t n)
{
pi = new int[n];
}

/// Constructeur postblit
this(this)
{
// duplique le tableau pointe par pi
pi = pi.dup;
}
}

Pourquoi un constructeur postblit ?

 

À ce stade-là, vous vous demanderez peut-être quel peut être l'intérêt du postblit constructeur par rapport à ce bon vieux constructeur de copie C++. C'est simple. Avec la contrainte que les objets doivent être déplaçables, une copie d'objets devient strictement équivalente à un déplacement + un appel à un hook défini par l'utilisateur (ici le postblit). Bon d'accord, le ramasse-miettes simplifie aussi un peu les choses.

Lorsque le compilateur détecte qu'il peut remplacer une copie par un déplacement, il est libre de ne pas insérer d'appel au constructeur postblit. En somme, D remplace la définition de 2 constructeurs (copie et déplacement) par la définition d'une seule méthode (postblit) qui ne sera pas appelée s'il n'y en a pas besoin.

Repost 0
Published by Olivier - dans Programmation
commenter cet article
14 octobre 2010 4 14 /10 /octobre /2010 17:20

Déplacement contre copie

La nouvelle norme du langage C++ introduit une nouveauté de taille, les rvalue references, destinées à réduire le nombre de copies d'objets en privilégiant le déplacement d'un objet plutôt que sa copie lorsque cela est possible. Une rvalue reference est une référence vers un objet anonyme temporaire et s'écrit au moyen de deux symboles & au lieu d'un seul.

En gros, la forme canonique d'une classe T en C++ 11 ressemblera à ça :

 class T
{
int* pi;
public:
T() : pi(new int[100000]) { }
~T() { delete[] pi; }

// Sémantique de copie
T(const T& t) : pi(new int[100000]) { memcpy(pi, t.pi,
100000 * sizeof(int)); }
T& operator=(const T& t) {memcpy(pi, t.pi, 100000 * sizeof(int));}

// Sémantique de déplacement
T(T&& t) : pi(t.pi) {t.pi = nullptr; }
T& operator=(T&& t) {std::swap(pi, t.pi); return *this; }
};

 

Avec en dernier, les deux nouvelles méthodes à implémenter, destinées à être utilisées lorsque l'objet sera déplacé plutôt que copié. On peut noter que le paramètre de ces méthodes n'est pas constant, puisque celui-ci est destiné à être invalidé par le déplacement.

 

Le constructeur de déplacement sera utilisé par le compilateur lorsqu'on affectera un temporaire anonyme, par exemple avec le code suivant :

 

 T creerGrosObjet()
{
T obj1;
return obj1;
}

int main(int argc, char** argv)
{
T obj2 = creerGrosObjet(); // On ne copie pas, on déplace.
return 0;
}

 

 

Dans cet exemple, un compilateur C++ 11 détectera que l'objet affecté à la variable obj2 est un temporaire destiné à être détruit immédiatement et fera donc appel au constructeur de déplacement pour que ob2.pi pointe sur les mêmes données que obj1.pi (qui lui se verra affecter null, sans cela, l'appel au destructeur de obj1 aurait de fâcheuses conséquences).

Comme un petit dessin vaut mieux qu'un long discours, voici une illustration destinée à mettre en évidence la différence entre un déplacement et une copie d'objets.

:

 

  copyvsmove

 

Pour plus d'informations sur les rvalue references, vous pouvez vous référer à http://www.artima.com/cppsource/rvalue.html

Premier benchmark

 

Un premier benchmark de la STL tirant parti de cette optimisation a été posté sur le net et le résultat est plutôt sympathique puisqu'on a presque un facteur 14 de performances entre le test utilisant la copie et celui utilisant le déplacement.

 

http://cpp-next.com/archive/2010/10/howards-stl-move-semantics-benchmark/

 

On peut donc s'attendre à avoir un gain appréciable de performances des programmes C++, dès lors que le commun des mortels aura compris comment les rvalue references fonctionnent.

Car c'est bien là le coeur du problème : les rvalues references viennent complexifier un langage qui n'en avait pas vraiment besoin.

Repost 0
Published by Olivier - dans Programmation
commenter cet article
5 octobre 2010 2 05 /10 /octobre /2010 16:06

 

As my relatives may know, I am fond of Digital Mars D programming language. It allies both C++ power and modern languages ease of use, like Java or C#.

When you read a criticism of the D programming language, one of the flaws that come the most often is related to the toolchain. OPTLINK, the Windows linker comes to my mind immediately (thank God DMD UNIX version use the GCC linker instead of this one). But the most problematic issue is certainly the lack of a good IDE. 

Of course, a lot of good text editors do have a D mode by now, but you often still have to compile and debug your application in a seperate terminal window. If GDB last version now supports D, there was a long way to a integration in a good IDE.

Rainer Schuetze may be our messiah, having written a Visual Studio plugin called, you had guessed it, Visual D.

 

 

screenshot

 

Let's have a look how to install it correctly on Windows.

 

 

First step, download the compiler.

 

Visual D do not provies a D compiler by itself, it only launches it and communicates with it. So you have to download the official Digital Mars compiler, DMD. Last version is 2.049 by now. It comes as a zip archive that you just have to decompress in a folder. For the need of this article, I have chosen the C:\dmd2 folder.

 

 

Second step, Visual Studio

 

If you are the happy owner of a non Express Visual Studio version, please make sure it is installed correctly and skip the following lines.

If Visual Studio Express versions are free of charge, they do not include plugin support, so you cannot use them to host the Visual D plugin. You have to download and install something called Visual Studio 2010 Shell instead. It is in fact the Visual Studio standard edition IDE, without any language support. It is not meant to be used directly, but as a plugin platform. We are going to use it as a Visual D host. It is provided by Microsoft for free. Once you have downloaded and installed it, you can go to the next section.

 

Visual, at last

 

Now download and install Visual D. At the time these lines are written, version 0.3.17 has just came out of the bake. This is the version I use.

If you have several Visual Studio versions installed on your computer, be sure to choose the 2010 version during the installation process. When you will be asked to, indicate the folder where you had DMD unzipped at step one (c:\dmd2 if you did the way I did).

Now you can start Visual Studio 2010 from your Start menu. If everything went right, you will  see D project templates on the "File / new project "dialog. Just try to create a "hello world"  console application to ensure you installation is correct.

 

projects

Don't worry about this ".Net Framework 4" thiing on the top of the screen, DMD will only generate 100% native code !

Cannot add symbols to module, probably msobj100.dll missing

 

Whenever I was trying to compile and run an application in the IDE, I got this error message. It seems that the missing msobj100.dll file is not provided by Visual Studio 2010 Shell. Fortunately, it comes with Visual C++ 2010. I was able to resolve the issue by downloading and installing Visual C++ 2010 Express and now everything works correctly for me. 

Repost 0
Published by Olivier - dans Programmation
commenter cet article
4 octobre 2010 1 04 /10 /octobre /2010 17:15

 

Ceux qui me connaissent le savent bien, je suis un adepte du langage D de Digital Mars. Ce langage a le mérite d'allier la puissance du C++ à la facilité d'utilisation d'un langage plus moderne tels Java ou C#.

Un des gros points noirs qui est souvent cité lorsqu'on élabore une critique du langage est la faiblesse des utilitaires officiels. Je pense bien évidement à OPTLINK, l'éditeur de liens sous Windows (les versions UNIX de DMD ont la chance d'utiliser celui de GCC), mais aussi et surtout au manque d'environnement de développement intégré. Certes de nombreux éditeurs de texte possèdent un mode D, mais il faut encore souvent se payer la compilation et le débogage de son application dans un terminal séparé. Si GDB supporte maintenant officiellement le langage, il restait encore pas mal de chemin vers une intégration à un IDE.

La lumière vient de nous être apportée par Rainer Schuetze et son plugin pour Visual Studio, logiquement nommé Visual D.

 

screenshot

 

Nous allons voir comment installer cet environnement de développement gratuit sous Windows.

 

 

Première étape, Télécharger le compilateur.

 

Visual D ne contient pas en lui même le compilateur D. Il ne fait que l'appeler et communiquer avec lui. Vous devez donc télécharger le compilateur officiel de Digital Mars, DMD. À l'heure où j'écris ces lignes, il est en version 2.049. Téléchargez donc l'archive contenant le compilateur et décompressez-le. J'ai pour ma part opté pour le dossier C:\dmd2.

 

Deuxième étape, Visual Studio

 

Si vous êtes l'heureux propriétaire d'une version payante de Visual D, assurez-vous de l'avoir bien installée et passez au prochain paragraphe.Sinon, lisez la suite.

Les versions Express de Visual Studio ont beau être gratuites, elles ne supportent pas l'installation de plugins et vous ne pouvez donc pas les utiliser comme hôte pour Visual D. Vous devez donc télécharger et installer le Visual Studio 2010 Shell, lui aussi disponible gratuitement. Le Shell correspond à l'IDE version standard, dépouillé de tout support de langage. On ne peut donc pas s'en servir directement. C'est juste une plateforme à plugins. Nous allons l'utiliser comme hôte pour Visual D.

 

Enfin, Visual D

 

Téléchargez maintenant Visual D. Au moment où j'écris ces lignes, la version 0.3.17 vient tout juste de sortir (c'est celle que j'utilise). Si vous avez plusieurs versions de Visual Studio, choisissez d'installer Visual D dans Visual Studio 2010. Indiquez-lui en outre le répertoire racine de l'installation de DMD (dans mon cas, C:\dmd2).

Vous pouvez lancer Visual Studio depuis le menu démarrer. Si Visual D est correctement installé, vous aurez alors des modèles de projets D dans la liste de nouveaux projets. Créez une application console et vérifiez qu'elle s'exécute bien.

 

projects

Ne prêtez pas attention à la mention .Net Framework 4 en haut de l'écran. DMD ne va générer que du code natif !

Cannot add symbols to module, probably msobj100.dll missing

 

J'avais cette erreur qui m'empêchait d'exécuter ou de déboguer toute application au sein de l'environnement de développement. En effet la version gratuite du Shell ne fournit pas cette bibliothèque. Elle est cependant fournie par Visual C++. J'ai résolu ce problème en installant la version Express de Visual C++.

Repost 0
Published by Olivier - dans Programmation
commenter cet article

Présentation

Recherche

Liens