January 29, 2026 (2mo ago)

Classes vs Structs : Le guide du développeur sur les performances

Découvrez les différences fondamentales entre classes et structs. Apprenez quand utiliser l'un ou l'autre pour un code propre et hautes performances en C#, Swift, C++, et plus.

← Back to blog
Cover Image for Classes vs Structs : Le guide du développeur sur les performances

Découvrez les différences fondamentales entre classes et structs. Apprenez quand utiliser l'un ou l'autre pour un code propre et hautes performances en C#, Swift, C++, et plus.

Classes vs Structs : Guide du développeur sur les performances

Résumé : Découvrez les différences fondamentales entre classes et structs. Apprenez quand utiliser l'un ou l'autre pour un code propre et hautes performances en C#, Swift, C++, et plus.

Introduction

La différence fondamentale entre classes et structs est simple mais décisive : les classes sont des types par référence et les structs sont des types par valeur. Cette distinction détermine la disposition en mémoire, le comportement de copie et les performances à l'exécution dans des langages comme C#, C++ et Swift. Savoir quand choisir l'un plutôt que l'autre est essentiel pour obtenir un code prévisible et des systèmes performants.

Comprendre la différence fondamentale

Lorsque vous instanciez une classe, la variable que vous détenez est une référence qui pointe vers l'objet sur le tas. Copier cette variable copie la référence ; plusieurs références peuvent pointer vers le même objet, donc les modifications effectuées via une référence sont visibles depuis les autres. Cette sémantique par référence est une raison clé pour laquelle les classes sont utilisées pour des entités avec identité.1

Un struct représente les données elles-mêmes. Créer un struct produit un paquet concret de valeurs — souvent stocké sur la pile ou en ligne dans des tableaux — donc copier un struct donne un duplicata indépendant. Modifier la copie n'affecte pas l'original, ce qui rend les structs excellents pour des valeurs simples et immuables.1

Pour en savoir plus sur l'encapsulation et la conception orientée objet, consultez notre guide sur l'encapsulation orientée objet : https://cleancodeguy.com/blog/object-oriented-encapsulation.

Diagramme illustrant les Structs stockés sur la pile et les Classes référencées depuis la pile vers le tas.

Comparaison rapide : types par référence vs types par valeur

CaractéristiqueClasse (Type par référence)Struct (Type par valeur)
Emplacement en mémoireTas ; objet référencé par pointeur.Pile ou en ligne ; la variable contient les données.
AffectationCopie la référence, pas l'objet.Copie la valeur entière.
Durée de vieGérée par le ramasse-miettes (ou suppression manuelle dans certains langages).Désalloué hors portée ou lorsqu'il est stocké en ligne.
Identité vs valeurA une identité ; plusieurs références peuvent pointer vers une instance.Représente une valeur ; l'égalité est souvent basée sur les données.

Utilisez une classe lorsque vous avez besoin d'une identité partagée. Utilisez un struct lorsque vous avez besoin d'une valeur simple et autonome qui peut être copiée sans effets secondaires.

Cette base informe des compromis de performance plus profonds — allocation tas vs pile, localité de cache et pression sur le ramasse-miettes — que nous explorons ci‑dessous.

Comment l'allocation mémoire dicte la vitesse

La disposition en mémoire affecte l'efficacité du CPU, le débit et la latence. L'accès à une classe implique typiquement une indirection : un pointeur sur la pile référence des données sur le tas. Cette recherche supplémentaire ajoute un coût et peut nuire au comportement du cache. Les structs, stockés directement à l'endroit où la variable vit, évitent souvent cette indirection et permettent des dispositions mémoire plus compactes avec une meilleure localité de cache.1

Diagramme comparant l'allocation mémoire Struct/Pile et Class/Tas, mettant en évidence la mémoire contiguë, le ramasse-miettes, et les différences de vitesse.

Coûts du ramasse-miettes

Les objets sur le tas sont soumis au ramasse-miettes. Les cycles GC peuvent suspendre l'exécution, augmentant la latence dans des systèmes temps réel ou à haut débit. L'allocation fréquente d'objets de classe de courte durée augmente la pression sur le GC et la charge CPU. Utiliser des types par valeur pour de nombreux petits objets de courte durée réduit le churn du tas et le travail du GC.3

L'allocation sur le tas ajoute un potentiel surcoût lié au ramasse-miettes. Les structs évitent ce surcoût lorsqu'ils restent basés sur la valeur et non boxés.

Ceci est particulièrement important dans les systèmes conçus pour l'échelle et la réactivité — réduire les allocations réduit directement l'activité du GC et peut lisser les performances à l'exécution.

Localité du cache et débit

Les CPU modernes reposent sur des caches. Les dispositions séquentielles — comme des tableaux de structs — améliorent les hits de cache et le débit. Des allocations séparées sur le tas pour chaque instance de classe dispersent les données en mémoire, augmentant les misses de cache et ralentissant le traitement. Pour les boucles serrées et les pipelines de traitement de données, les dispositions contiguës de valeurs constituent un avantage important.5

Le piège du boxing

Le boxing se produit lorsqu'un type par valeur est converti en type par référence (par exemple, lorsqu'il est placé dans une collection qui attend des objets). Le boxing alloue un objet sur le tas et copie la valeur dedans, annulant les avantages de performance du struct et augmentant la charge sur le GC. Éviter le boxing est un principe clé de l'utilisation efficace des types par valeur.4

Comment les langages diffèrent : C#, C++ et Swift

Différents langages imposent des conventions et des capacités différentes. Connaître les règles spécifiques à chaque langage évite d'appliquer aveuglément des règles d'un langage à un autre.

Diagramme comparant les caractéristiques des langages de programmation C#, C++ et Swift, en se concentrant sur les types par référence vs valeur et la flexibilité des objets.

C# : modèle clair référence vs valeur

En C#, class = type par référence et struct = type par valeur. Utilisez des classes pour des entités avec identité (par exemple, Customer ou DatabaseConnection) et des structs pour de petites valeurs immuables (par exemple, Point, Color). Garder les structs petits et immuables évite des bugs subtils et des frais de copie.1

Les erreurs courantes incluent la création de structs volumineux ou mutables ; les deux conduisent à des bugs surprenants ou à des régressions de performance. Suivez la règle des structs petits et immuables lors d'optimisations en C#.

C++ : convention plutôt que restriction du langage

En C++, la seule différence syntaxique entre struct et class est l'accessibilité par défaut. Les deux peuvent être alloués sur la pile ou le tas, avoir des méthodes et supporter l'héritage. La convention est d'utiliser struct pour des agrégats de données simples et class pour des objets encapsulés et la gestion RAII des ressources.

Cette flexibilité signifie que les développeurs C++ doivent s'appuyer sur des conventions et des choix de conception plutôt que sur des distinctions valeur/référence imposées par le langage. Pour des conseils sur les modèles de polymorphisme et d'héritage, consultez nos notes de conception C++ : https://cleancodeguy.com/blog/polymorphism-vs-inheritance.

Swift : orientation valeur par défaut

Swift encourage la préférence des structs pour la plupart des types personnalisés. Les structs en Swift supportent les méthodes, les extensions et la conformité aux protocoles, ce qui les rend puissants tout en étant des valeurs sûres par défaut. Choisissez les classes uniquement lorsque la sémantique par référence, l'identité ou l'interopérabilité Objective‑C est requise.2

Cette conception axée sur la valeur favorise l'immuabilité et une meilleure compréhension du flux de données, en particulier dans du code concurrent.

Quand choisir un struct pour une efficacité maximale

Les structs sont idéaux pour de petits paquets de données immuables dont l'identité est entièrement définie par leurs valeurs. Exemples typiques :

  • Données géométriques : Point2D ou RGBColor
  • Valeurs financières : Money (amount + currency)
  • Petits DTO utilisés dans des pipelines à haut débit

Une règle pratique de taille est la règle « 16–32 octets » : si les champs d'un struct tiennent à peu près dans cette plage, le coût de copie est modeste et souvent moins cher que l'allocation sur le tas. Si un struct devient plus grand ou doit être mutable, une classe est probablement un meilleur choix.5

Guide sur le choix de structs pour de petites données de type valeur comme RGB, Point 2D et Money, avec une directive de 16 octets.

Règles d'immuabilité et de taille

  • Préférez les structs immuables : les valeurs doivent être créées une fois et remplacées plutôt que mutées.
  • Gardez les structs petits : copier fréquemment de gros structs peut devenir plus coûteux que passer des références.

Ces règles aident à éviter des bugs silencieux (dus aux copies mutables) et des pièges de performance (dus à des copies excessives ou au boxing).

Pièges courants et refactorings

Deux problèmes courants sont les structs mutables et le boxing excessif.

Les structs mutables conduisent à des comportements surprenants car les modifications n'affectent qu'une copie. Refactorez les structs mutables en structs immuables qui renvoient de nouvelles instances pour les changements d'état.

Le boxing se produit implicitement dans de nombreuses API et collections ; identifiez et supprimez les points chauds de boxing pour préserver les avantages de performance des structs.

Exemple : refactoriser un Point mutable en struct immuable (C#)

// PITFALL: Mutable struct public struct MutablePoint { public int X { get; set; } public int Y { get; set; }

public void Move(int dx, int dy)
{
    X += dx;
    Y += dy;
}

}

// REFACTOR: Immutable struct public readonly struct ImmutablePoint { public int X { get; } public int Y { get; }

public ImmutablePoint(int x, int y)
{
    X = x;
    Y = y;
}

public ImmutablePoint MovedBy(int dx, int dy)
{
    return new ImmutablePoint(X + dx, Y + dy);
}

}

Ce refactoring rend l'intention explicite et élimine la corruption d'état accidentelle. Pour plus de pratiques de clean coding, consultez notre guide des principes : https://cleancodeguy.com/blog/clean-coding-principles.

Foire aux questions (Q&R concise)

Q1 : Quand devrais‑je préférer un struct à une classe ?

R : Préférez un struct lorsque le type est petit, immuable et représente une valeur plutôt qu'une identité. Les structs excellent pour des données simples comme des points, des couleurs ou de petits DTO.

Q2 : Quels pièges de performance dois‑je surveiller ?

R : Évitez les structs mutables, les structs volumineux (coût de copie) et le boxing en objets sur le tas — ces facteurs annulent les bénéfices des types par valeur et peuvent nuire aux performances.

Q3 : Comment les différences de langage affectent‑elles mon choix ?

R : Suivez les idiomes du langage : C# impose la distinction valeur vs référence ; C++ repose sur la convention ; Swift favorise les types par valeur par défaut. Apprenez les règles de la plateforme avant d'appliquer des modèles multi‑langages.12


Chez Clean Code Guy, nous aidons les équipes à appliquer ces principes sur de vrais codebases. Nos nettoyages de code et refactorings AI‑ready rendent les logiciels plus rapides, plus sûrs et plus faciles à maintenir. Visitez https://cleancode.com pour en savoir plus.

1.
Microsoft Docs, “Choosing between classes and structs,” https://learn.microsoft.com/en-us/dotnet/standard/choosing-between-class-and-struct
2.
Apple Developer Documentation, “Structures and Classes,” https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html
4.
5.
NDepend Blog, “Class vs Struct in C#: Making Informed Choices,” https://blog.ndepend.com/class-vs-struct-in-c-making-informed-choices/
← Back to blog
🙋🏻‍♂️

L’IA écrit du code.
Vous le faites durer.

À l’ère de l’accélération de l’IA, le code propre n’est pas seulement une bonne pratique — c’est la différence entre les systèmes qui évoluent et les codebases qui s’effondrent sous leur propre poids.