std :: একটি অসম্পূর্ণ টাইপ সহ অনন্য_পিটার সংকলন করবে না


202

আমি এর সাথে পিম্পল-আইডিয়ামটি ব্যবহার করছি 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অসম্পূর্ণ প্রকারের সাথে ব্যবহার করতে সক্ষম হওয়া উচিত। এটি কি লাইবসি ++ এ একটি বাগ বা আমি এখানে কিছু ভুল করছি?


সম্পূর্ণতার প্রয়োজনীয়তার জন্য রেফারেন্স লিংক: stackoverflow.com/a/6089065/576911
হাওয়ার্ড হিন্যান্ট

1
একটি পিম্পল প্রায়শই নির্মিত হয় এবং তখন থেকে সংশোধিত হয় না। আমি সাধারণত একটি স্টাডি :: শেয়ারড_পিটার <কনট উইন্ডো_আইপিএল>
এমএফএনএক্স

সম্পর্কিত: আমি এমএসভিসিতে কেন এটি কাজ করে এবং কীভাবে এটি কাজ করা থেকে রোধ করা যায় তা জানতে খুব আগ্রহী (যাতে আমি আমার জিসিসি সহকর্মীদের সংকলন না ভাঙ্গি)।
লেন

উত্তর:


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
আমি আপনার প্রথম সমাধানটি খুঁজে পেয়েছি ( foo ডেস্ট্রাক্টর যুক্ত করে ) শ্রেণি ঘোষণাকে নিজেই সংকলন করতে দেয় তবে সেই ধরণের কোনও বস্তু ঘোষণার ফলে যে কোনও জায়গায় আসল ত্রুটির ("আকারের 'অবৈধ অ্যাপ্লিকেশন ...") পাওয়া যায়।
জেফ ট্রল

38
দুর্দান্ত উত্তর, শুধু খেয়াল করার জন্য; আমরা এখনও foo::~foo() = default;
ডিআরএলটি

2
টেমপ্লেট কনস্ট্রাক্টরদের সাথে বেঁচে থাকার এক উপায় হ'ল ক্লাস বডিতে কনস্ট্রাক্টরকে ঘোষণা করা কিন্তু সংজ্ঞায়িত করা না, এটি কোথাও সংজ্ঞায়িত করে সম্পূর্ণ ইমপ্লিট সংজ্ঞা দেখা যায় এবং সেখানে সমস্ত প্রয়োজনীয় ইনস্ট্যান্টেশন স্পষ্টভাবে ইনস্ট্যান্টিয়েট করা হয়।
এনোবায়রাম

2
আপনি কীভাবে ব্যাখ্যা করতে পারেন যে এটি কিছু ক্ষেত্রে কীভাবে কাজ করবে এবং অন্যগুলিতেও না? আমি পিপল আইডিয়মটি একটি অনন্য_পিটার এবং কোনও শ্রেণিবিহীন শ্রেণীর সাথে ব্যবহার করেছি এবং অন্য একটি প্রকল্পে আমার কোড ওপির উল্লিখিত ত্রুটির সাথে সংকলন করতে ব্যর্থ হয়েছে ..
কৌতূহলী

1
দেখে মনে হয় যে ইউনিক_পিটারের জন্য ডিফল্ট মানটি সি ++ 11 শৈলীর সাথে শ্রেণীর শিরোলেখ ফাইলটিতে ull nullptr to সেট করা থাকে, উপরোক্ত কারণে একটি সম্পূর্ণ ঘোষণাও প্রয়োজন।
ফিরাওয়ানি

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(সি ++ 14 থেকে উপলভ্য) এর ব্যবহারকে বাতিল করে, যেমনটি এখানে ইতিমধ্যে আলোচনা করা হয়েছে


6
আমি যতটা উদ্বিগ্ন এটিই সঠিক সমাধান। পিম্পল-আইডিয়ামটি ব্যবহার করা এটি অনন্য নয়, অসম্পূর্ণ ক্লাস সহ স্ট্যান্ড :: অনন্য_পিটার ব্যবহার করে এটি একটি সাধারণ সমস্যা। স্ট্যান্ড :: অনন্য_পটি <এক্স> দ্বারা ব্যবহৃত ডিফল্ট মুছক "এক্স মুছে ফেলুন" করার চেষ্টা করে, এটি এক্স পার্সওয়ার্ড ডিক্লেয়ারেশন হলে এটি করতে পারে না। একটি মুছে ফেলা ফাংশন নির্দিষ্ট করে, আপনি সেই ফাংশনটি কোনও উত্স ফাইলে রাখতে পারেন যেখানে দশম শ্রেণি সম্পূর্ণরূপে সংজ্ঞায়িত। অন্যান্য উত্স ফাইলগুলি তখন স্ট্যান্ড :: ইউনিক_প্রেটার <এক্স, ডেলিটারফঙ্ক> ব্যবহার করতে পারে যদিও ডেলিটরফঙ্কযুক্ত উত্স ফাইলের সাথে লিংক হওয়া পর্যন্ত এক্স কেবলমাত্র একটি অগ্রণী ঘোষণা forward
শেলটন্ড

1
এটি যখন আপনার "ফু" টাইপের একটি উদাহরণ তৈরি করে অবশ্যই কোনও ইনলাইন ফাংশন সংজ্ঞা থাকতে পারে (উদাহরণস্বরূপ স্থির "getInstance" পদ্ধতি যা নির্মাতা এবং ধ্বংসকারীকে রেফারেন্স করে), এবং আপনি এগুলি একটি বাস্তবায়ন ফাইলে স্থানান্তর করতে চান না @ অ্যাডস্পেক্স 5 এর পরামর্শ অনুসারে
গেমসলুটস

20

একটি কাস্টম মুছুন ব্যবহার করুন

সমস্যাটি হ'ল unique_ptr<T>ডিস্ট্রাক্টরকে T::~T()তার নিজস্ব ডেস্ট্রাক্টর, তার মুভ অ্যাসাইনমেন্ট অপারেটর এবং unique_ptr::reset()সদস্য ফাংশনটিতে (কেবল) কল করতে হবে। তবে এগুলি অবশ্যই বেশ কয়েকটি পিআইএমপিএল পরিস্থিতিতে (ইতিমধ্যে বাহ্যিক শ্রেণীর ডেস্ট্রাক্টর এবং মুভ অ্যাসাইনমেন্ট অপারেটরটিতে) ডাকা উচিত (স্পষ্ট বা স্পষ্টতই)।

হিসাবে ইতিমধ্যেই অন্য উত্তরে নির্দিষ্ট, এক উপায় যে এড়াতে সরানো হয় সব অপারেশন যে প্রয়োজন unique_ptr::~unique_ptr(), unique_ptr::operator=(unique_ptr&&)এবং unique_ptr::reset()সোর্স ফাইল যেখানে pimpl সাহায্যকারী বর্গ আসলে সংজ্ঞায়িত করা হয় মধ্যে।

যাইহোক, এটি বরং অসুবিধাগুলি এবং pimpl idoim এর একেবারে বিন্দুটিকে কিছুটা ডিগ্রী দেয়। একটি অনেক ক্লিনার সমাধান যা কাস্টম মোছার ব্যবহার করে এবং মুদ্রা সহায়ক সহায়ক শ্রেণি যেখানে থাকে কেবল উত্স ফাইলে তার সংজ্ঞাটি স্থানান্তরিত করে তা থেকে বিরত থাকে। এখানে একটি সহজ উদাহরণ:

// 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 ফাইলে থাকতে হবে। উইন্ডো_আইপিএল এর জন্যও ...

বিটিডব্লিউ, আপনাকে আপনার .h ফাইলে উইন্ডোজ ক্লাসের জন্য স্পষ্টভাবে ডেস্ট্রাক্টর ডিক্লেয়ারেশন যুক্ত করতে হবে।

তবে আপনি শিরোনাম ফাইলটিতে খালি ডটর বডি রাখতে পারবেন না:

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

অবশ্যই কেবল একটি ঘোষণা হতে হবে:

  class window {
    virtual ~window();
  }

এটি আমার সমাধানও ছিল। উপায় আরও সংক্ষিপ্ত। কেবলমাত্র আপনার কনস্ট্রাক্টর / ডেস্ট্রাক্টরকে শিরোনামে ঘোষণা করা হয়েছে এবং সিপিপি ফাইলে সংজ্ঞায়িত করা হয়েছে।
ক্রিস মর্নেস

2

কাস্টম মোছার বিষয়ে অন্যের জবাব যুক্ত করতে, আমাদের অভ্যন্তরীণ "ইউটিলিটিস লাইব্রেরি" তে আমি এই সাধারণ প্যাটার্নটি বাস্তবায়নের জন্য একটি সহায়ক শিরোনাম যুক্ত করেছি ( std::unique_ptrঅসম্পূর্ণ প্রকারের, যেমন টিউর কয়েকটিতে জানা যায় যেমন দীর্ঘ সংকলনের সময় এড়াতে বা সরবরাহ করতে ক্লায়েন্টদের কাছে কেবল একটি অস্বচ্ছ হ্যান্ডেল)।

এটি এই প্যাটার্নটির জন্য সাধারণ স্ক্যাফোোল্ডিং সরবরাহ করে: একটি কাস্টম ডিলেটর ক্লাস যা একটি বাহ্যিকভাবে সংজ্ঞায়িত মুছকযন্ত্র ফাংশন, unique_ptrএই মুছক শ্রেণীর সাথে একটির জন্য একটি টাইপ উপন্যাস এবং একটি টিইউতে মুছে ফাংশন ঘোষণা করার জন্য একটি ম্যাক্রো যার সম্পূর্ণ সংজ্ঞা রয়েছে has টাইপ করুন। আমি মনে করি এটির কিছু সাধারণ উপযোগিতা রয়েছে তাই এখানে এটি রয়েছে:

#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

সেরা সমাধান নাও হতে পারে তবে কখনও কখনও আপনি পরিবর্তে শেয়ারড_পিটার ব্যবহার করতে পারেন । অবশ্যই যদি এটি কিছুটা ওভারকিল হয় তবে ... অনন্য_পিটার হিসাবে, আমি সম্ভবত 10 বছর অপেক্ষা করব যতক্ষণ না সি ++ স্ট্যান্ডার্ড নির্মাতারা ল্যাম্বডাকে ডিলেটর হিসাবে ব্যবহার করার সিদ্ধান্ত নেবেন।

অন্য দিক। আপনার কোড অনুযায়ী এটি হতে পারে, ধ্বংসের মঞ্চে উইন্ডো_আইপিএল অসম্পূর্ণ হবে। এটি অপরিবর্তিত আচরণের কারণ হতে পারে। এটি দেখুন: কেন, সত্যই, একটি অসম্পূর্ণ প্রকার মুছে ফেলা অপরিজ্ঞাত আচরণ?

সুতরাং, সম্ভব হলে আমি ভার্চুয়াল ডেস্ট্রাক্টর সহ আপনার সমস্ত অবজেক্টের জন্য খুব বেস অবজেক্টটি সংজ্ঞায়িত করব। এবং আপনি প্রায় ভাল। আপনার কেবল মনে রাখা উচিত যে সিস্টেমটি আপনার পয়েন্টারের জন্য ভার্চুয়াল ডেস্ট্রাক্টরকে কল করবে, সুতরাং আপনার প্রতিটি পূর্বপুরুষের জন্য এটি সংজ্ঞায়িত করা উচিত। এছাড়াও আপনি একটি ভার্চুয়াল যেমন উত্তরাধিকার বিভাগে বেস বর্গ (দেখুন সংজ্ঞায়িত করা উচিত এই বিস্তারিত জানার জন্য)।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.