Après avoir lu cette réponse , il semble que c'est une meilleure pratique d'utiliser pointeurs intelligents autant que possible, et de réduire au minimum l'utilisation de pointeurs "normaux"/bruts .
Est-ce vrai?
Non ce n'est pas vrai. Si une fonction a besoin d'un pointeur et n'a rien à voir avec la propriété, je crois fermement qu'un pointeur normal doit être transmis pour les raisons suivantes:
shared_ptr
, alors vous ne pourrez pas passer, disons, scoped_ptr
La règle serait la suivante - si vous savez qu'une entité doit prendre un certain type de propriété de l'objet, toujours use pointeurs intelligents - celui qui vous donne le type de propriété dont vous avez besoin. S'il n'y a pas de notion de propriété, jamais utilisez des pointeurs intelligents.
Exemple 1:
void PrintObject(shared_ptr<const Object> po) //bad
{
if(po)
po->Print();
else
log_error();
}
void PrintObject(const Object* po) //good
{
if(po)
po->Print();
else
log_error();
}
Exemple2:
Object* createObject() //bad
{
return new Object;
}
some_smart_ptr<Object> createObject() //good
{
return some_smart_ptr<Object>(new Object);
}
L'utilisation de pointeurs intelligents pour gérer la propriété est la bonne chose à faire. Inversement, l'utilisation de pointeurs bruts partout où la propriété n'est pas un problème est pas incorrecte.
Voici quelques utilisations parfaitement légitimes des pointeurs bruts (rappelez-vous, on suppose toujours qu'ils ne sont pas propriétaires):
où ils rivalisent avec les références
0
pour la durée de vie de l'objetstd::bind
Utilise une convention où les arguments passés sont copiés dans le foncteur résultant; cependant std::bind(&T::some_member, this, ...)
ne fait qu'une copie du pointeur tandis que std::bind(&T::some_member, *this, ...)
copie l'objet; std::bind(&T::some_member, std::ref(*this), ...)
est une alternativeoù ils le font pas en concurrence avec les références
boost::optional<T&>
boost::optional<T&>
Pour rappel, il est presque toujours erroné d'écrire une fonction (qui n'est pas un constructeur ou un membre de fonction qui, par exemple, prend possession) qui accepte un pointeur intelligent à moins qu'il ne le transmette à son tour à un constructeur (par exemple, il est correct pour std::async
Car sémantiquement, il est proche d'être un appel au constructeur std::thread
). S'il est synchrone, pas besoin de pointeur intelligent.
Pour récapituler, voici un extrait qui illustre plusieurs des utilisations ci-dessus. Nous écrivons et utilisons une classe qui applique un foncteur à chaque élément d'un std::vector<int>
Tout en écrivant une sortie.
class apply_and_log {
public:
// C++03 exception: it's acceptable to pass by pointer to const
// to avoid apply_and_log(std::cout, std::vector<int>())
// notice that our pointer would be left dangling after call to constructor
// this still adds a requirement on the caller that v != 0 or that we throw on 0
apply_and_log(std::ostream& os, std::vector<int> const* v)
: log(&os)
, data(v)
{}
// C++0x alternative
// also usable for C++03 with requirement on v
apply_and_log(std::ostream& os, std::vector<int> const& v)
: log(&os)
, data(&v)
{}
// now apply_and_log(std::cout, std::vector<int> {}) is invalid in C++0x
// && is also acceptable instead of const&&
apply_and_log(std::ostream& os, std::vector<int> const&&) = delete;
// Notice that without effort copy (also move), assignment and destruction
// are correct.
// Class invariants: member pointers are never 0.
// Requirements on construction: the passed stream and vector must outlive *this
typedef std::function<void(std::vector<int> const&)> callback_type;
// optional callback
// alternative: boost::optional<callback_type&>
void
do_work(callback_type* callback)
{
// for convenience
auto& v = *data;
// using raw pointers as iterators
int* begin = &v[0];
int* end = begin + v.size();
// ...
if(callback) {
callback(v);
}
}
private:
// association: we use a pointer
// notice that the type is polymorphic and non-copyable,
// so composition is not a reasonable option
std::ostream* log;
// association: we use a pointer to const
// contrived example for the constructors
std::vector<int> const* data;
};
L'utilisation de pointeurs intelligents est toujours recommandée car ils documentent clairement la propriété.
Ce qui nous manque vraiment, cependant, est un pointeur intelligent "vierge", qui n'implique aucune notion de propriété.
template <typename T>
class ptr // thanks to Martinho for the name suggestion :)
{
public:
ptr(T* p): _p(p) {}
template <typename U> ptr(U* p): _p(p) {}
template <typename SP> ptr(SP const& sp): _p(sp.get()) {}
T& operator*() const { assert(_p); return *_p; }
T* operator->() const { assert(_p); return _p; }
private:
T* _p;
}; // class ptr<T>
C'est, en effet, la version la plus simple de tout pointeur intelligent qui puisse exister: un type qui documente qu'il ne possède pas la ressource qu'il pointe également.
Un exemple où le comptage de références (utilisé par shared_ptr en particulier) se décompose est lorsque vous créez un cycle à partir des pointeurs (par exemple, A pointe vers B, B pointe vers A ou A-> B-> C-> A, ou etc). Dans ce cas, aucun des objets ne sera jamais automatiquement libéré, car ils conservent tous un décompte de références supérieur à zéro.
Pour cette raison, chaque fois que je crée des objets qui ont une relation parent-enfant (par exemple une arborescence d'objets), j'utiliserai shared_ptrs dans les objets parents pour contenir leurs objets enfants, mais si les objets enfants ont besoin d'un pointeur vers leur parent , Je vais utiliser un simple pointeur C/C++ pour cela.
Je pense qu'une réponse un peu plus approfondie a été donnée ici: Quel type de pointeur dois-je utiliser quand?
Extrait de ce lien: "Utilisez des pointeurs stupides (pointeurs bruts) ou des références pour des références non propriétaires vers des ressources et lorsque vous savez que le la ressource survivra à l'objet/portée de référence. " (gras conservé de l'original)
Le problème est que si vous écrivez du code à usage général, il n'est pas toujours facile d'être absolument certain que l'objet survivra au pointeur brut. Considérez cet exemple:
struct employee_t {
employee_t(const std::string& first_name, const std::string& last_name) : m_first_name(first_name), m_last_name(last_name) {}
std::string m_first_name;
std::string m_last_name;
};
void replace_current_employees_with(const employee_t* p_new_employee, std::list<employee_t>& employee_list) {
employee_list.clear();
employee_list.Push_back(*p_new_employee);
}
void main(int argc, char* argv[]) {
std::list<employee_t> current_employee_list;
current_employee_list.Push_back(employee_t("John", "Smith"));
current_employee_list.Push_back(employee_t("Julie", "Jones"));
employee_t* p_person_who_convinces_boss_to_rehire_him = &(current_employee_list.front());
replace_current_employees_with(p_person_who_convinces_boss_to_rehire_him, current_employee_list);
}
À sa grande surprise, la fonction replace_current_employees_with()
peut par inadvertance entraîner la désallocation de l'un de ses paramètres avant qu'il n'ait fini de l'utiliser.
Ainsi, même si au premier abord il peut sembler que la fonction replace_current_employees_with()
n'a pas besoin de s'approprier ses paramètres, elle a besoin d'une sorte de défense contre la possibilité que ses paramètres soient insidieusement désalloués avant d'avoir fini de les utiliser. La solution la plus simple consiste à s'approprier (partager temporairement) le ou les paramètres, probablement via un shared_ptr
.
Mais si vous ne voulez vraiment pas vous approprier, il existe maintenant une option sûre - et c'est la partie plug sans vergogne de la réponse - " pointeurs enregistrés ". Les "pointeurs enregistrés" sont des pointeurs intelligents qui se comportent comme des pointeurs bruts, sauf qu'ils sont (automatiquement) définis sur null_ptr
lorsque l'objet cible est détruit et, par défaut, lèvera une exception si vous essayez d'accéder à un objet qui a déjà été supprimé.
Notez également que les pointeurs enregistrés peuvent être "désactivés" (remplacés automatiquement par leur homologue de pointeur brut) par une directive de compilation, ce qui leur permet d'être utilisés (et entraînent des frais généraux) en mode débogage/test/bêta uniquement. Donc, vous devriez vraiment avoir recours à des pointeurs bruts réels assez rarement.
Quelques cas, où vous voudrez peut-être utiliser des pointeurs: