Pour conserver les implémentations de templates C++ dans un fichier source séparé sur macOS, instanciez explicitement les types pris en charge là où leurs définitions sont visibles, puis liez ce fichier objet à votre exécutable. Cette procédure pas à pas crée un petit exemple local avec les outils C++ d'Apple.
La tâche est spécifique : compiler un programme C++ dont les corps des membres de template résident dans un fichier .cpp. L'exemple prend en charge deux types concrets. Une section ultérieure explique les exigences supplémentaires lorsqu'une application de bureau Swift consomme la même interface C++.
Pourquoi le déplacement du code de template dans un fichier source casse l'édition de liens ?
Un template décrit une famille de types ou de fonctions. La compilation de son fichier d'implémentation ne produit pas automatiquement toutes les spécialisations possibles. L'instanciation d'une classe n'instancie pas non plus automatiquement le corps de chaque fonction membre ; les membres sont généralement instanciés lorsque requis. Ces distinctions expliquent pourquoi un appelant peut compiler avec succès tout en laissant des références non résolues au moment de l'édition de liens. Projet de travail C++ : Instanciation implicite
Considérez un en-tête qui définit la structure d'un template de classe et déclare son constructeur et ses méthodes. Un appelant peut comprendre ces déclarations. Mais si les corps des méthodes n'existent que dans un autre fichier source, l'appelant ne peut pas utiliser ces corps pour générer les fonctions requises. Le fichier d'implémentation doit fournir les instanciations concrètes dont l'appelant a besoin.
Une définition d'instanciation explicite demande ces instanciations. Une déclaration extern template identifie une instanciation fournie ailleurs. Les définitions d'implémentation doivent être disponibles dans l'unité de traduction fournissant la définition d'instanciation explicite. Projet de travail C++ : Instanciation explicite
Choisissez l'arrangement selon qui contrôle les arguments de type :
| Exigence du lecteur | Arrangement approprié | Conséquence de maintenance |
|---|---|---|
| Les appelants peuvent fournir des types appropriés arbitraires | Placez les définitions dans l'en-tête, ou dans un en-tête d'implémentation inclus depuis celui-ci | Les appelants peuvent instancier de nouveaux types |
| La bibliothèque prend en charge un ensemble connu de types | Conservez les corps dans un fichier source et instanciez explicitement cet ensemble | Ajoutez une instanciation lors de l'ajout d'un type pris en charge |
| Une application de bureau n'a besoin que d'une opération concrète | Exposez un wrapper C++ ordinaire autour d'une spécialisation sélectionnée | Conservez les choix de template à l'intérieur de l'implémentation C++ |
Cet outil d'aide à la décision découle des règles d'instanciation ci-dessus. Pour cette procédure, l'ensemble pris en charge est délibérément limité à int et double.
Construire un exemple complet en trois fichiers
Utilisez un nouveau dossier vide et enregistrez les trois fichiers suivants ensemble. Le programme synthétique ajoute un décalage à une valeur ; les petites entrées maintiennent les résultats attendus faciles à inspecter.
Préparer les outils de ligne de commande d'Apple
Apple fournit le compilateur et le SDK macOS via Xcode ou le paquet autonome Command Line Tools. Si aucun n'est installé, ouvrez Terminal et exécutez la commande suivante, puis terminez la boîte de dialogue d'installation. Une installation séparée des outils est inutile lorsque Xcode est déjà installé. Installation des outils de ligne de commande
xcode-select --install
Définir l'interface publique
Enregistrez cet en-tête complet sous Offset.hpp. Conservez la définition de la classe, y compris son membre de données, visible pour les appelants. Seuls le constructeur et les corps des méthodes passent dans le fichier source.
#ifndef OFFSET_HPP
#define OFFSET_HPP
namespace demo {
template <typename T>
class Offset {
public:
explicit Offset(T amount);
T apply(T value) const;
private:
T amount_;
};
extern template class Offset<int>;
extern template class Offset<double>;
} // namespace demo
#endif
Les deux lignes extern template déclarent les instanciations fournies ; elles ne fournissent pas les corps des membres. Une définition de template de classe doit rester accessible pour cet arrangement d'instanciation explicite au niveau de la classe. Projet de travail C++ : Instanciation explicite
Définir les corps et fournir les instanciations
Enregistrez cette implémentation complète sous Offset.cpp. Placez les définitions d'instanciation explicite après les définitions des membres.
#include "Offset.hpp"
namespace demo {
template <typename T>
Offset<T>::Offset(T amount) : amount_(amount) {}
template <typename T>
T Offset<T>::apply(T value) const {
return value + amount_;
}
template class Offset<int>;
template class Offset<double>;
} // namespace demo
L'ordre importe : une définition d'instanciation explicite au niveau de la classe ne couvre que les membres définis à ce stade. Elle n'instancie pas non plus automatiquement les templates de membres. Conservez un fichier source propriétaire pour chaque définition d'instanciation explicite ; le langage autorise au maximum une par template et ensemble d'arguments dans le programme. Instanciation explicite, Instanciation et spécialisation de templates : Général
Appeler les deux types pris en charge
Enregistrez cet appelant complet sous main.cpp.
#include "Offset.hpp"
#include <iostream>
int main() {
const demo::Offset<int> whole(3);
const demo::Offset<double> fractional(0.5);
const int first = whole.apply(4);
const double second = fractional.apply(2.0);
std::cout << first << '\n' << second << '\n';
return (first == 7 && second == 2.5) ? 0 : 1;
}
Le programme vérifie ses résultats via son statut de sortie ainsi que par leur affichage. Cet exemple a été inspecté pour sa cohérence mais n'a pas été compilé lors de la préparation : l'environnement de recherche ne disposait d'aucun compilateur C++ ni SDK macOS.
Compiler les deux fichiers sources, puis lier les deux fichiers objets
Dans Terminal, accédez au dossier contenant les trois fichiers. Exécutez ces commandes dans l'ordre, en continuant seulement lorsque chaque commande précédente réussit :
xcrun clang++ -std=c++17 -stdlib=libc++ -Wall -Wextra -pedantic -c Offset.cpp -o Offset.o
xcrun clang++ -std=c++17 -stdlib=libc++ -Wall -Wextra -pedantic -c main.cpp -o main.o
xcrun clang++ -std=c++17 -stdlib=libc++ main.o Offset.o -o template-demo
./template-demo
Ici, -c produit un fichier objet sans édition de liens. La dernière invocation du compilateur omet -c et combine les deux objets en un exécutable. Les commandes sélectionnent explicitement C++17 et libc++ plutôt que de s'appuyer sur les valeurs par défaut. Guide des commandes du compilateur Clang
La sortie attendue, déduite des entrées synthétiques, est :
7
2.5
La dépendance essentielle est visible dans la dernière commande de construction : main.o demande les fonctions, et Offset.o fournit leurs instanciations. Dans un projet plus vaste, assurez-vous que la cible contenant Offset.cpp est construite et que son objet ou sa bibliothèque atteint l'étape d'édition de liens de l'exécutable. Une compilation réussie de ce fichier seul ne complète pas l'édition de liens.
N'ajoutez pas #include "Offset.cpp" à l'appelant tout en le compilant séparément. Pour cet arrangement, incluez l'en-tête public et compilez l'implémentation comme sa propre unité de traduction. Si les appelants ont besoin des corps, choisissez délibérément l'arrangement d'en-tête d'implémentation inclus du tableau précédent.
Diagnostiquer les instanciations manquantes dans un ordre utile
Identifiez d'abord la fonction non résolue exacte, y compris ses arguments de template et ses qualificatifs. Suivez ensuite cette séquence. Les vérifications appliquent la distinction documentée entre la génération de fichiers objets et leur édition de liens, conjointement aux exigences d'instanciation explicite. Étapes de compilation Clang, Règles d'instanciation explicite C++
| Ce que vous trouvez | Ce qu'il faut vérifier ou modifier |
|---|---|
| Les deux types pris en charge ont des membres non résolus | Confirmez que Offset.o apparaît dans la commande d'édition de liens finale |
| Un type nouvellement utilisé échoue | Ajoutez son instanciation correspondante au fichier d'implémentation propriétaire et sa déclaration à l'en-tête |
| Une méthode manque | Confirmez que son corps précède l'instanciation de la classe et que sa déclaration correspond à la définition |
| Un template de membre manque | Fournissez l'instanciation de template de membre requise séparément, avec sa définition visible |
| Un diagnostic de définition en double apparaît | Vérifiez les définitions d'instanciation répétées ou un fichier d'implémentation compilé et inclus |
| Le diagnostic nomme une architecture incompatible | Vérifiez l'architecture de chaque objet et bibliothèque liés |
Par exemple, modifier l'appelant pour utiliser demo::Offset<float> introduit un type que cette implémentation ne fournit pas. Pour le prendre en charge, ajoutez extern template class Offset<float>; dans l'espace de noms de l'en-tête et template class Offset<float>; après les corps dans Offset.cpp, puis reconstruisez les deux objets. La simple addition de la déclaration ne génère pas les fonctions manquantes.
Si l'exécutable llvm-nm séparé de LLVM est installé et disponible sur votre chemin de commande, ces commandes optionnelles inspectent les objets de l'exemple :
llvm-nm --demangle --undefined-only main.o
llvm-nm --demangle --defined-only Offset.o
Recherchez les noms de membres demo::Offset correspondants dans les symboles non résolus de l'appelant et les symboles définis de l'implémentation. D'autres entrées sont attendues, y compris les références à la bibliothèque standard. LLVM documente à la fois les filtres et le dénommage des noms ; ces commandes requièrent spécifiquement llvm-nm, dont la disponibilité ne doit pas être supposée à partir d'une installation des outils Apple. Guide de la commande llvm-nm
Préserver la compatibilité d'architecture et d'ABI macOS
L'instanciation explicite fournit du code de template concret ; elle ne rend pas un fichier objet utilisable pour chaque architecture. Le flux de travail binaire universel d'Apple compile et lie des versions spécifiques à l'architecture avant de les combiner. Si l'application prend en charge à la fois arm64 et x86_64, incluez la source d'instanciation dans les deux constructions. Construction d'un binaire macOS universel
Pour une construction universelle de l'exemple, inspectez l'exécutable résultant avec la commande suivante. Vérifiez que ses architectures signalées correspondent à la construction prévue ; les commandes de construction ordinaires précédentes ne demandent pas de binaire universel.
xcrun lipo -archs ./template-demo
Conserver les corps des méthodes séparés laisse également la structure de la classe visible dans l'en-tête. Traitez les modifications de cette structure comme des changements d'interface nécessitant la reconstruction du code dépendant. N'inférez pas une interface binaire stable de la présence d'instanciations explicites. libc++ documente des garanties de compatibilité et des limites de configuration spécifiques, et distingue explicitement ces garanties de celles concernant les fonctions définies par l'utilisateur. Garanties ABI de libc++
Que change-t-il lorsqu'une application Swift appelle le code C++ ?
Maintenez deux exigences séparées : Swift doit importer une déclaration C++ prise en charge, et l'exécutable final doit lier son implémentation. La documentation de Swift expose les spécialisations concrètes de template de classe via des alias de type C++ ; le template non instancié lui-même n'est pas directement disponible comme type Swift. Mélanger Swift et C++ : Utiliser les templates de classe
Pour l'interface d'exemple, ce qui suit est un fragment d'en-tête optionnel à insérer dans namespace demo, après la définition de la classe. Il donne un nom au type concret pour l'importation :
using IntOffset = Offset<int>;
Cet alias nomme le type. L'instanciation explicite dans Offset.cpp fournit toujours le constructeur et la méthode hors ligne. Swift importe les en-têtes C++ en utilisant les modules Clang et ne peut actuellement pas importer les modules C++20. Suivez la configuration d'interopérabilité basée sur les en-têtes pour le projet. Mélanger Swift et C++
Dans Xcode, activez C++ and Objective-C interoperability avec la valeur C++ / Objective-C++ pour la cible Swift. Pour une cible d'application utilisant C++ depuis cette même cible, incluez l'en-tête C++ dans son en-tête de pont. Les cibles de framework exposent les déclarations prises en charge via des en-têtes publics. Configuration de projets Swift et C++ multi-langages
Le code Swift et C++ mélangé doit utiliser la même bibliothèque standard C++ ; les plateformes Apple utilisent libc++. L'interopérabilité nécessite C++14 ou version ultérieure. Faites correspondre le mode de langage des en-têtes importés à la construction C++, et vérifiez la liste des fonctionnalités prises en charge avant d'exposer des templates plus élaborés. L'instanciation explicite n'étend pas les correspondances de langage prises en charge par Swift. Fonctionnalités et contraintes prises en charge de l'interopérabilité C++
Pour un ensemble fixe de types, maintenez ensemble les déclarations publiques, la liste d'instanciation et les entrées de liaison finales. Lorsqu'un nouvel appelant a besoin d'un autre type, ajoutez ce type délibérément ou rendez l'implémentation disponible pour l'instanciation côté appelant. Ce choix maintient la limite des fichiers sources compréhensible au fur et à mesure que le projet de bureau grandit.
Sources
- Instanciation implicite — Projet de travail C++
- Instanciation explicite — Projet de travail C++
- Instanciation et spécialisation de templates : Général — Projet de travail C++
- Installation des outils de ligne de commande — Apple
- clang — le compilateur Clang C, C++ et Objective-C — Projet LLVM
- llvm-nm — lister la table des symboles du bitcode LLVM et du fichier objet — Projet LLVM
- Construction d'un binaire macOS universel — Apple
- Garanties ABI de libc++ — Projet LLVM
- Mélanger Swift et C++ — Swift.org
- Configuration de projets Swift et C++ multi-langages — Swift.org
- Fonctionnalités et contraintes prises en charge de l'interopérabilité C++ — Swift.org