std :: unique_ptr एक अपूर्ण प्रकार के साथ संकलन नहीं करेगा


203

मैं के साथ pimpl- मुहावरे का उपयोग कर रहा हूँ std::unique_ptr:

class window {
  window(const rectangle& rect);

private:
  class window_impl; // defined elsewhere
  std::unique_ptr<window_impl> impl_; // won't compile
};

हालाँकि, मुझे एक अपूर्ण प्रकार के उपयोग के बारे में एक संकलन त्रुटि मिलती है, लाइन 304 में <memory>:

sizeofअपूर्ण प्रकार ' uixx::window::window_impl' के लिए ' ' का अवैध आवेदन

जहाँ तक मुझे पता है, std::unique_ptrअधूरे प्रकार के साथ उपयोग करने में सक्षम होना चाहिए। क्या यह libc ++ में बग है या मैं यहां कुछ गलत कर रहा हूं?


पूर्णता आवश्यकताओं के लिए संदर्भ लिंक: stackoverflow.com/a/6089065/576911
हॉवर्ड हेनेंट

1
एक पिम्पल का निर्माण अक्सर किया जाता है और तब से संशोधित नहीं किया जाता है। मैं आमतौर पर एक std का उपयोग करता हूं :: साझा_प्रा <<window_impl>
mfnx

संबंधित: मैं यह जानना चाहूंगा कि यह MSVC में क्यों काम करता है, और इसे काम करने से कैसे रोका जाए (ताकि मैं अपने GCC सहयोगियों के संकलन को न तोड़ूँ)।
लेन

जवाबों:


258

यहाँ std::unique_ptrअपूर्ण प्रकार के साथ कुछ उदाहरण दिए गए हैं । समस्या विनाश में है।

यदि आप पिंपल का उपयोग करते हैं unique_ptr, तो आपको एक विध्वंसक घोषित करने की आवश्यकता है:

class foo
{ 
    class impl;
    std::unique_ptr<impl> impl_;

public:
    foo(); // You may need a def. constructor to be defined elsewhere

    ~foo(); // Implement (with {}, or with = default;) where impl is complete
};

क्योंकि अन्यथा कंपाइलर एक डिफ़ॉल्ट बनाता है, और उसे इसके foo::implलिए पूर्ण घोषणा की आवश्यकता होती है ।

यदि आपके पास टेम्पलेट निर्माता हैं, तो आप खराब हो जाते हैं, भले ही आप impl_सदस्य का निर्माण न करें :

template <typename T>
foo::foo(T bar) 
{
    // Here the compiler needs to know how to
    // destroy impl_ in case an exception is
    // thrown !
}

नाम स्थान के दायरे में, उपयोग unique_ptrकरने से भी काम नहीं होगा:

class impl;
std::unique_ptr<impl> impl_;

चूंकि संकलक को यह जानना चाहिए कि इस स्थिर अवधि ऑब्जेक्ट को कैसे नष्ट किया जाए। एक समाधान है:

class impl;
struct ptr_impl : std::unique_ptr<impl>
{
    ~ptr_impl(); // Implement (empty body) elsewhere
} impl_;

3
मैं आपका पहला समाधान खोजता हूं ( फू डिस्ट्रक्टर को जोड़ते हुए ) वर्ग घोषणा को स्वयं संकलित करने की अनुमति देता है, लेकिन उस प्रकार की एक वस्तु को कहीं भी घोषित करने से मूल त्रुटि होती है ("'आकार' का अमान्य अनुप्रयोग ...")।
जेफ ट्राल

38
उत्कृष्ट जवाब, बस ध्यान देने के लिए; हम अभी भी जैसे रखकर डिफ़ॉल्ट निर्माता / नाशक उपयोग कर सकते हैं foo::~foo() = default;src फ़ाइल में
विधानसभा

2
टेम्पलेट कंस्ट्रक्टर्स के साथ रहने का एक तरीका घोषित करना होगा लेकिन क्लास बॉडी में कंस्ट्रक्टर को परिभाषित नहीं करना है, इसे परिभाषित करें कहीं न कहीं पूरी तरह से निहित परिभाषा दिखाई देती है और स्पष्ट रूप से वहां सभी आवश्यक तात्कालिकता को तुरंत परिभाषित करता है।
enobayram

2
क्या आप बता सकते हैं कि यह कुछ मामलों में कैसे काम करेगा और दूसरों में नहीं होगा? मैंने pimpl मुहावरे का उपयोग एक यूनिक_प्रेट और बिना किसी डिस्ट्रक्टर के एक वर्ग के साथ किया है, और एक अन्य परियोजना में मेरा कोड ओपी द्वारा उल्लिखित त्रुटि के साथ संकलन करने में विफल रहता है ..
उत्सुक

1
ऐसा लगता है कि अनूठे_ptr के लिए डिफ़ॉल्ट मान c ++ 11 शैली के साथ वर्ग की हेडर फ़ाइल में {nullptr} पर सेट है, उपरोक्त कारण के लिए एक पूर्ण घोषणा की भी आवश्यकता है।
फेय्रेनसी

53

जैसा कि अलेक्जेंड्रे सी ने उल्लेख किया है, यह समस्या windowउन स्थानों पर विनाशकारी होने के लिए नीचे आती है जहां उन स्थानों को स्पष्ट रूप से परिभाषित किया गया है जहां window_implअभी भी अपूर्णता है। उनके समाधान के अलावा, मैंने जो एक और वर्कअराउंड का उपयोग किया है, वह हैडर में डीलेटर फंटर घोषित करना है:

// Foo.h

class FooImpl;
struct FooImplDeleter
{
  void operator()(FooImpl *p);
};

class Foo
{
...
private:
  std::unique_ptr<FooImpl, FooImplDeleter> impl_;
};

// Foo.cpp

...
void FooImplDeleter::operator()(FooImpl *p)
{
  delete p;
}

ध्यान दें कि एक कस्टम डीलेटर फ़ंक्शन का उपयोग std::make_uniqueपहले से ही चर्चा के अनुसार (C ++ 14 से उपलब्ध) के उपयोग को रोकता है


6
जहां तक ​​मेरा सवाल है, यह सही समाधान है। यह pimpl-idiom का उपयोग करने के लिए अद्वितीय नहीं है, यह std का उपयोग करने के साथ एक सामान्य समस्या है :: अपूर्ण वर्गों के साथ unique_ptr। Std :: unique_ptr <X> द्वारा उपयोग किए जाने वाले डिफ़ॉल्ट डिलेटर "डिलीट एक्स" करने का प्रयास करता है, जो कि एक्स फॉरवर्ड डिक्लेरेशन नहीं है। डिलेटर फ़ंक्शन को निर्दिष्ट करके, आप उस फ़ंक्शन को एक स्रोत फ़ाइल में डाल सकते हैं जहां कक्षा X पूरी तरह से परिभाषित है। अन्य स्रोत फाइलें तब std :: unique_ptr <X, DeleterFunc> का उपयोग कर सकती हैं, भले ही X केवल एक आगे की घोषणा हो, जब तक कि वे स्रोत फ़ाइल के साथ जुड़े हुए हैं DeleterFunc।
शेल्टोंड

1
जब आप अपने "फू" प्रकार (उदाहरण के लिए एक स्थिर "getInstance" विधि जो निर्माता और विध्वंसक को संदर्भित करते हैं) का उदाहरण बनाकर इनलाइन फ़ंक्शन की परिभाषा होनी चाहिए, तो यह एक अच्छा समाधान है, और आप इन्हें क्रियान्वयन फ़ाइल में स्थानांतरित नहीं करना चाहते हैं जैसा कि @ adspx5 बताता है।
गेमशूट्स

20

एक कस्टम deleter का उपयोग करें

समस्या यह है कि unique_ptr<T>विध्वंसक T::~T()को अपने स्वयं के विध्वंसक को, उसके चालन कार्य संचालक को, और unique_ptr::reset()सदस्य कार्य (केवल) को कॉल करना होगा । हालाँकि, इन्हें कई PIMPL स्थितियों (पहले से ही बाहरी वर्ग के विध्वंसक और चाल असाइनमेंट ऑपरेटर) में कहा जाना चाहिए (स्पष्ट या स्पष्ट रूप से)।

जैसा कि पहले ही एक और जवाब में बताया, एक ही रास्ता है कि से बचने के लिए ले जाने के लिए है सभी कार्य है कि आवश्यकता होती है unique_ptr::~unique_ptr(), unique_ptr::operator=(unique_ptr&&)और unique_ptr::reset()स्रोत फ़ाइल जहां pimpl सहायक वर्ग वास्तव में परिभाषित किया गया है में।

हालांकि, यह असुविधाजनक है और कुछ हद तक पिंपल इडोइम के बिंदु को परिभाषित करता है। एक बहुत क्लीनर समाधान जो एक कस्टम डिलेटर का उपयोग करने से बचता है और केवल अपनी परिभाषा को स्रोत फ़ाइल में स्थानांतरित करता है जहां दाना सहायक वर्ग रहता है। ये रहा एक सरल उदाहरण:

// file.h
class foo
{
  struct pimpl;
  struct pimpl_deleter { void operator()(pimpl*) const; };
  std::unique_ptr<pimpl,pimpl_deleter> m_pimpl;
public:
  foo(some data);
  foo(foo&&) = default;             // no need to define this in file.cc
  foo&operator=(foo&&) = default;   // no need to define this in file.cc
//foo::~foo()          auto-generated: no need to define this in file.cc
};

// file.cc
struct foo::pimpl
{
  // lots of complicated code
};
void foo::pimpl_deleter::operator()(foo::pimpl*ptr) const { delete ptr; }

एक अलग डिलेटर वर्ग के बजाय, आप एक फ्री फ़ंक्शन या लैंबडा के साथ संयोजन के staticसदस्य का उपयोग कर सकते हैं foo:

class foo {
  struct pimpl;
  static void delete_pimpl(pimpl*);
  std::unique_ptr<pimpl,[](pimpl*ptr){delete_pimpl(ptr);}> m_pimpl;
};

15

संभवतः आपके पास .h फ़ाइल में वर्ग के भीतर कुछ फ़ंक्शन बॉडीज़ हैं जो अपूर्ण प्रकार का उपयोग करता है।

सुनिश्चित करें कि आपके .h वर्ग विंडो के लिए आपके पास केवल फ़ंक्शन घोषणा है। विंडो के लिए सभी फ़ंक्शन बॉडी .cpp फ़ाइल में होनी चाहिए। और window_impl के लिए भी ...

Btw, आपको स्पष्ट रूप से अपनी .h फ़ाइल में विंडोज़ वर्ग के लिए विध्वंसक घोषणा को जोड़ना होगा।

लेकिन आप हेडर फ़ाइल में खाली डीटोर बॉडी नहीं डाल सकते हैं:

class window {
    virtual ~window() {};
  }

केवल एक घोषणा होनी चाहिए:

  class window {
    virtual ~window();
  }

यह मेरा समाधान भी था। रास्ता अधिक संक्षिप्त। बस आपके कंस्ट्रक्टर / डिस्ट्रक्टर को हेडर में घोषित किया गया है और सीपीपी फाइल में परिभाषित किया गया है।
क्रिश मॉर्नेस

2

हमारे आंतरिक "उपयोगिताओं पुस्तकालय" में कस्टम डिलेटर के बारे में दूसरे के उत्तरों को जोड़ने के लिए, मैंने इस सामान्य पैटर्न को लागू करने के लिए एक सहायक हेडर जोड़ा ( std::unique_ptrएक अपूर्ण प्रकार का, जिसे केवल कुछ टीयू के लिए जाना जाता है। उदाहरण के लिए लंबे संकलन समय से बचने के लिए या प्रदान करने के लिए बस ग्राहकों के लिए एक अपारदर्शी संभाल)।

यह इस पैटर्न के लिए आम मचान प्रदान करता है: एक कस्टम डिलेटर क्लास जो बाहरी परिभाषित डिलेटर फ़ंक्शन को आमंत्रित करता है, unique_ptrइस डेलेटर क्लास के साथ एक प्रकार का उपनाम और एक टीयू में डीलेटर फ़ंक्शन को घोषित करने के लिए एक मैक्रो। प्रकार। मुझे लगता है कि इसकी कुछ सामान्य उपयोगिता है, इसलिए यहां यह है:

#ifndef CZU_UNIQUE_OPAQUE_HPP
#define CZU_UNIQUE_OPAQUE_HPP
#include <memory>

/**
    Helper to define a `std::unique_ptr` that works just with a forward
    declaration

    The "regular" `std::unique_ptr<T>` requires the full definition of `T` to be
    available, as it has to emit calls to `delete` in every TU that may use it.

    A workaround to this problem is to have a `std::unique_ptr` with a custom
    deleter, which is defined in a TU that knows the full definition of `T`.

    This header standardizes and generalizes this trick. The usage is quite
    simple:

    - everywhere you would have used `std::unique_ptr<T>`, use
      `czu::unique_opaque<T>`; it will work just fine with `T` being a forward
      declaration;
    - in a TU that knows the full definition of `T`, at top level invoke the
      macro `CZU_DEFINE_OPAQUE_DELETER`; it will define the custom deleter used
      by `czu::unique_opaque<T>`
*/

namespace czu {
template<typename T>
struct opaque_deleter {
    void operator()(T *it) {
        void opaque_deleter_hook(T *);
        opaque_deleter_hook(it);
    }
};

template<typename T>
using unique_opaque = std::unique_ptr<T, opaque_deleter<T>>;
}

/// Call at top level in a C++ file to enable type %T to be used in an %unique_opaque<T>
#define CZU_DEFINE_OPAQUE_DELETER(T) namespace czu { void opaque_deleter_hook(T *it) { delete it; } }

#endif

1

सबसे अच्छा समाधान नहीं हो सकता है, लेकिन कभी-कभी आप इसके बजाय शेयर्ड_प्ट्र का उपयोग कर सकते हैं । यदि बेशक यह थोड़ा अधिक है, लेकिन ... के रूप में Unique_ptr के लिए, मैं शायद 10 साल और इंतजार करूंगा जब तक कि C ++ मानक निर्माता लैम्ब्डा को डीलेटर के रूप में उपयोग करने का फैसला नहीं करेंगे।

दूसरी तरफ़। आपके कोड के अनुसार ऐसा हो सकता है, कि विनाश चरण पर window_impl अधूरा होगा। यह अपरिभाषित व्यवहार का एक कारण हो सकता है। इसे देखें: क्यों, वास्तव में, अपूर्ण प्रकार को हटाना अपरिभाषित व्यवहार है?

इसलिए, यदि संभव हो तो मैं आभासी विध्वंसक के साथ आपकी सभी वस्तुओं के लिए एक बहुत ही आधार वस्तु को परिभाषित करूंगा। और आप लगभग अच्छे हैं। आपको बस यह ध्यान रखना चाहिए कि सिस्टम आपके पॉइंटर के लिए वर्चुअल डिस्ट्रक्टर को बुलाएगा, इसलिए आपको इसे प्रत्येक पूर्वज के लिए परिभाषित करना चाहिए। तुम भी एक आभासी रूप में विरासत खंड में आधार वर्ग (देखें परिभाषित करना चाहिए इस जानकारी के लिए)।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.