std :: unique_ptr ile eksik tip derlenmez


203

Ben pimpl-deyim ile kullanıyorum std::unique_ptr:

class window {
  window(const rectangle& rect);

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

Ancak, eksik bir tür kullanımı ile ilgili derleme hatası alıyorum, satır 304 <memory>:

' sizeof' Eksik bir tür ' uixx::window::window_impl' için geçersiz uygulama

Bildiğim kadarıyla, std::unique_ptreksik tip ile kullanılabilmelidir. Bu libc ++ 'da bir hata mı yoksa burada yanlış bir şey mi yapıyorum?



1
Bir sivilce genellikle inşa edilir ve o zamandan beri değiştirilmez. Genellikle bir std :: shared_ptr <const window_impl> kullanıyorum
mfnx

İlgili: Bunun neden MSVC'de çalıştığını ve çalışmasının nasıl önleneceğini bilmek istiyorum (böylece GCC meslektaşlarımın derlemelerini kırmıyorum).
Len

Yanıtlar:


260

std::unique_ptrEksik türlerle ilgili bazı örnekler . Sorun yıkımda yatıyor.

İle pimpl kullanıyorsanız unique_ptr, bir yıkıcı beyan etmeniz gerekir:

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
};

çünkü aksi takdirde derleyici varsayılan bir tane üretir ve foo::impl .

Şablon yapıcılarınız varsa, impl_üyeyi oluşturmasanız bile vidalanırsınız :

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

Ad alanı kapsamında, kullanma unique_ptrişlevi de çalışmaz:

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

çünkü derleyici burada bu statik süre nesnesinin nasıl yok edileceğini bilmelidir. Geçici çözüm:

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

3
Ben ilk çözüm bulmak ( foo destructor ekleyerek ) sınıf bildirimi derlemek kendisi sağlar, ancak bu tür bir nesne herhangi bir yerde bildirmek özgün hata ("sizeof '... geçersiz uygulama) ile sonuçlanır.
Jeff Trull

38
mükemmel cevap, sadece not etmek için; Yine de, örneğin foo::~foo() = default;src dosyasına yerleştirerek varsayılan kurucuyu /
yıkıcıyı kullanabiliriz

2
Şablon yapıcılarıyla yaşamanın bir yolu, yapıcıyı sınıf gövdesinde bildirmek, ancak tanımlamamak, tam impl tanımının görüldüğü bir yerde tanımlamak ve gerekli tüm örneklemeleri açıkça somutlaştırmak olacaktır.
enobayram

2
Bunun bazı durumlarda nasıl işe yarayacağını ve diğerlerinde nasıl işe yaramayacağını açıklayabilir misiniz? Ben pimpl deyim bir unique_ptr ve hiçbir yıkıcı ile bir sınıf kullandık ve başka bir projede benim kod belirtilen hata OP ile derlemek için başarısız ..
Meraklı

1
Görünüşe göre c ++ 11 stili ile sınıfın başlık dosyasında unique_ptr için varsayılan değer {nullptr} olarak ayarlanmışsa, yukarıdaki nedenle de tam bir bildirim gereklidir.
feirainy

53

Şöyle Alexandre C bahsedilen problemi aşağı gelir windowtürü burada bireyin yıkıcı dolaylı yerlerde tanımlanan window_impleksik devam etmektedir. Çözümlerine ek olarak, kullandığım başka bir geçici çözüm, başlıkta bir Deleter işlev belirtmektir:

// 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;
}

Özel bir Silme işlevi kullanmanın, buradastd::make_unique daha önce açıklandığı gibi (C ++ 14'ten edinilebilir) kullanımını engellediğini unutmayın .


6
Bu benim için doğru çözüm. Pimpl-idiom'u kullanmak benzersiz değildir, eksik sınıflarla std :: unique_ptr kullanmak genel bir sorundur. Std :: unique_ptr <X> tarafından kullanılan varsayılan silme işlemi, X ileriye dönük bir bildirim ise yapamayacağı "X'i sil" işlemini dener. Bir silme işlevi belirterek, bu işlevi X sınıfının tamamen tanımlandığı bir kaynak dosyaya koyabilirsiniz. Daha sonra X, DeleterFunc içeren kaynak dosyayla bağlantılı oldukları sürece ileriye dönük bir bildirim olsa da, diğer kaynak dosyaları std :: unique_ptr <X, DeleterFunc> kullanabilir.
Sheltond

1
"Foo" türünüzün bir örneğini (örneğin, yapıcı ve yıkıcıya başvuran statik bir "getInstance" yöntemi) oluşturmak için bir satır içi işlev tanımına sahip olmanız ve bunları bir uygulama dosyasına taşımak istemiyorsanız, bu iyi bir çözümdür. @ adspx5'in önerdiği gibi.
GameSalutes

20

özel bir silici kullan

Sorun, unique_ptr<T>yıkıcıyı T::~T()kendi yıkıcısında, taşıma atama işlecinde ve unique_ptr::reset()üye işlevinde (yalnızca) çağırması gerektiğidir . Ancak, bunlar birkaç PIMPL durumunda (örtük olarak veya açıkça) çağrılmalıdır (zaten dış sınıfın yıkıcı ve taşıma atama işlecinde).

Zaten başka cevap belirttiği gibi, bu kaçınmanın tek yol taşımaktır tüm gerektiren işlemleri unique_ptr::~unique_ptr(), unique_ptr::operator=(unique_ptr&&)veunique_ptr::reset() Pimpl yardımcı sınıfı aslında tanımlanır kaynak dosyası içine.

Bununla birlikte, bu oldukça elverişsizdir ve pimpl idoim'in noktasını bir dereceye kadar reddeder. Bir kullanmaktır tüm önler Daha net bir çözüm özel deleter ve sadece kaynak dosyadan sivilce yardımcı sınıfı hayatlarını içine tanımını taşıyın. İşte basit bir örnek:

// 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; }

Ayrı bir silme sınıfı yerine, bir lambda ile birlikte ücretsiz bir işlev veya staticüyesi de kullanabilirsiniz foo:

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

15

Muhtemelen sınıf içinde .h dosyası içinde eksik tür kullanan bazı işlev gövdeleriniz var.

Sınıf pencereniz için .h içinde sadece işlev bildirimine sahip olduğunuzdan emin olun. Pencere için tüm işlev gövdeleri .cpp dosyasında olmalıdır. Ve window_impl için de ...

Btw, .h dosyanızda açıkça windows sınıfı için yıkıcı bildirimi eklemeniz gerekir.

Ancak başlık dosyasına boş bir dtor gövdesi koyamazsınız:

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

Sadece bir beyan olmalı:

  class window {
    virtual ~window();
  }

Bu da benim çözümümdü. Çok daha özlü. Yapıcı / yıkıcınızı başlıkta bildirmeniz ve cpp dosyasında tanımlamanız yeterlidir.
Kris Morness

2

Kişinin özel silme hakkındaki yanıtlarına eklemek için dahili "yardımcı programlar kitaplığımıza" bu ortak kalıbı uygulamak için bir yardımcı başlık ekledim ( std::unique_ptreksik türden, yalnızca bazı TU tarafından bilinen, örneğin uzun derleme sürelerinden kaçınmak veya sağlamak için Müşterilere sadece opak bir tutamaç).

Bu model için ortak iskele sağlar: harici olarak tanımlanmış bir silme işlevini çağıran özel bir silme sınıfı, unique_ptrbu silme sınıfına sahip bir için bir tür diğer adı ve tam bir tanımlaması olan bir TU'daki silme işlevini bildiren bir makro yazın. Bunun genel bir faydası olduğunu düşünüyorum, işte burada:

#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

En iyi çözüm olmayabilir, ancak bazen bunun yerine shared_ptr kullanabilirsiniz . Tabii biraz aşırıya kaçarsa, ama ... unique_ptr gelince, belki C ++ standart üreticileri lambda'yı bir silici olarak kullanmaya karar verene kadar 10 yıl daha bekleyeceğim.

Diğer taraf. Kodunuza göre imha aşamasında window_impl eksik olacaktır. Bu, tanımlanmamış davranışların bir nedeni olabilir. Şuna bakın: Neden, tamamlanmamış bir türün silinmesi tanımsız bir davranıştır?

Yani, mümkünse sanal yıkıcı ile tüm nesnelerinize çok temel bir nesne tanımlayacağım. Ve neredeyse iyisin. Sadece sistemin işaretçiniz için sanal yıkıcı arayacağını aklınızda bulundurmalısınız, bu yüzden her ata için tanımlamanız gerekir. Ayrıca sanal olarak miras bölümünde taban sınıf (bkz tanımlamak gerekir bu detaylar için).

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.