En codant des choses après avoir pris l’indice de la réponse de ma précédente question , je suis tombé sur un problème de surcharge de Scene :: addObject.
Pour réitérer les bits pertinents et rendre cela autonome, avec le moins de détails possible:
Interface
dont il y a Foo
s et Bar
s;Scene
qui possède ces objets;Foo
s doivent être unique_ptr
s et Bar
s doivent être shared_ptr
s dans ma colonne principale (pour les raisons expliquées à la question précédente);main
les passe à l'instance Scene
, qui en prend possession.Exemple de code minimal est this :
#include <memory>
#include <utility>
class Interface
{
public:
virtual ~Interface() = 0;
};
inline Interface::~Interface() {}
class Foo : public Interface
{
};
class Bar : public Interface
{
};
class Scene
{
public:
void addObject(std::unique_ptr<Interface> obj);
// void addObject(std::shared_ptr<Interface> obj);
};
void Scene::addObject(std::unique_ptr<Interface> obj)
{
}
//void Scene::addObject(std::shared_ptr<Interface> obj)
//{
//}
int main(int argc, char** argv)
{
auto scn = std::make_unique<Scene>();
auto foo = std::make_unique<Foo>();
scn->addObject(std::move(foo));
// auto bar = std::make_shared<Bar>();
// scn->addObject(bar);
}
Si vous décommentez les lignes commentées, vous obtenez:
error: call of overloaded 'addObject(std::remove_reference<std::unique_ptr<Foo, std::default_delete<Foo> >&>::type)' is ambiguous
scn->addObject(std::move(foo));
^
main.cpp:27:6: note: candidate: 'void Scene::addObject(std::unique_ptr<Interface>)'
void Scene::addObject(std::unique_ptr<Interface> obj)
^~~~~
main.cpp:31:6: note: candidate: 'void Scene::addObject(std::shared_ptr<Interface>)'
void Scene::addObject(std::shared_ptr<Interface> obj)
^~~~~
Le fait de commenter le contenu partagé et de commenter le contenu unique compile également. Par conséquent, je suppose que le problème réside, comme le dit le compilateur, dans la surcharge. Cependant, j'ai besoin de la surcharge car ces deux types devront être stockés dans une sorte de collection, et ils sont en effet conservés comme des pointeurs vers la base (éventuellement tous déplacés dans shared_ptr
s).
Je passe les deux par valeur car je tiens à préciser que je prends possession de Scene
(et que le compteur de références pour le shared_ptr
s est supérieur). Je ne vois pas très bien où est le problème et je n’ai trouvé aucun exemple ailleurs.
Le problème que vous rencontrez est le constructeur de shared_ptr
(13) , (ce qui n’est pas explicite), correspond aussi bien qu’un constructeur similaire du type "moving dérived to base" de unique_ptr
(6) ( pas non plus explicite).
template< class Y, class Deleter >
shared_ptr( std::unique_ptr<Y,Deleter>&& r ); // (13)
13) Construit un
shared_ptr
qui gère l’objet actuellement géré parr
. Le deleter associé àr
est stocké pour une suppression ultérieure de l'objet géré.r
ne gère aucun objet après l'appel.Cette surcharge ne participe pas à la résolution de la surcharge si
std::unique_ptr<Y, Deleter>::pointer
n'est pas compatible avecT*
. Sir.get()
est un pointeur NULL, cette surcharge est équivalente au constructeur par défaut (1). (depuis C++ 17)
template< class U, class E >
unique_ptr( unique_ptr<U, E>&& u ) noexcept; //(6)
6) Construit un
unique_ptr
en transférant la propriété deu
à*this
, oùu
est construit avec un deleter spécifié (E).Ce constructeur ne participe à la résolution de surcharge que si toutes les conditions suivantes sont remplies:
a)
unique_ptr<U, E>::pointer
est implicitement convertible en pointeurb)
U
n'est pas un type de tableauc) Soit
Deleter
est un type de référence etE
est du même type queD
, ouDeleter
n'est pas un type de référence etE
est implicitement convertible enD
Dans le cas non polymorphe, vous construisez un unique_ptr<T>
à partir d'un unique_ptr<T>&&
, qui utilise le constructeur de déplacement sans modèle. Il résolution de surcharge préfère le non-modèle
Je vais supposer que Scene
enregistre shared_ptr<Interface>
s. Dans ce cas, vous n'avez pas besoin de surcharger addObject
pour unique_ptr
, vous pouvez simplement autoriser la conversion implicite dans l'appel.
L'autre réponse explique l'ambiguïté et une solution possible. Voici une autre solution au cas où vous auriez besoin des deux surcharges. vous pouvez toujours ajouter un autre paramètre dans de tels cas pour lever l'ambiguïté et utiliser la distribution de balises. Le code de la plaque de la chaudière est caché dans la partie privée de Scene
:
class Scene
{
struct unique_tag {};
struct shared_tag {};
template<typename T> struct tag_trait;
// Partial specializations are allowed in class scope!
template<typename T, typename D> struct tag_trait<std::unique_ptr<T,D>> { using tag = unique_tag; };
template<typename T> struct tag_trait<std::shared_ptr<T>> { using tag = shared_tag; };
void addObject_internal(std::unique_ptr<Interface> obj, unique_tag);
void addObject_internal(std::shared_ptr<Interface> obj, shared_tag);
public:
template<typename T>
void addObject(T&& obj)
{
addObject_internal(std::forward<T>(obj),
typename tag_trait<std::remove_reference_t<T>>::tag{});
}
};
Exemple compilable complet est ici.
Vous avez déclaré deux surcharges, une en prenant std::unique_ptr<Interface>
et une en prenant std::shared_ptr<Interface>
, mais transmettez un paramètre de type std::unique_ptr<Foo>
. Comme aucune de vos fonctions ne correspond directement, le compilateur doit effectuer une conversion pour appeler votre fonction.
Il existe une conversion disponible pour std::unique_ptr<Interface>
(conversion de type simple en pointeur unique vers la classe de base) et une autre en std::shared_ptr<Interface>
(modification d'un pointeur partagé vers la classe de base). Ces conversions ont la même priorité afin que le compilateur ne sache pas quelle conversion utiliser, vos fonctions sont donc ambiguës.
Si vous passez std::unique_ptr<Interface>
ou std::shared_ptr<Interface>
, aucune conversion n'est requise, il n'y a donc aucune ambiguïté.
La solution consiste simplement à supprimer la surcharge unique_ptr
et à toujours convertir en shared_ptr
. Cela suppose que les deux surcharges ont le même comportement. S'ils ne renomment pas l'une des méthodes, cela pourrait être plus approprié.
La solution de Jrok est déjà assez bonne. L'approche suivante permet de réutiliser le code encore mieux:
#include <memory>
#include <utility>
#include <iostream>
#include <type_traits>
namespace internal {
template <typename S, typename T>
struct smart_ptr_rebind_trait {};
template <typename S, typename T, typename D>
struct smart_ptr_rebind_trait<S,std::unique_ptr<T,D>> { using rebind_t = std::unique_ptr<S>; };
template <typename S, typename T>
struct smart_ptr_rebind_trait<S,std::shared_ptr<T>> { using rebind_t = std::shared_ptr<S>; };
}
template <typename S, typename T>
using rebind_smart_ptr_t = typename internal::smart_ptr_rebind_trait<S,std::remove_reference_t<T>>::rebind_t;
class Interface
{
public:
virtual ~Interface() = 0;
};
inline Interface::~Interface() {}
class Foo : public Interface {};
class Bar : public Interface {};
class Scene
{
void addObject_internal(std::unique_ptr<Interface> obj) { std::cout << "unique\n"; }
void addObject_internal(std::shared_ptr<Interface> obj) { std::cout << "shared\n"; }
public:
template<typename T>
void addObject(T&& obj) {
using S = rebind_smart_ptr_t<Interface,T>;
addObject_internal( S(std::forward<T>(obj)) );
}
};
int main(int argc, char** argv)
{
auto scn = std::make_unique<Scene>();
auto foo = std::make_unique<Foo>();
scn->addObject(std::move(foo));
auto bar = std::make_shared<Bar>();
scn->addObject(bar); // ok
}
Ce que nous faisons ici est d’abord introduire quelques classes d’aide permettant de relier les pointeurs intelligents.
Les foos doivent être unique_ptrs et les barres doivent être shared_ptrs dans mon main (pour les raisons expliquées à la question précédente);
Pouvez-vous surcharger en termes de pointeur-à -Foo
et de pointeur-à -Bar
au lieu de pointeur-à -Interface
, puisque vous souhaitez les traiter différemment?