Je pense que c'est une mauvaise stratégie à faire Derived_1::Impldériver Base::Impl.
L'objectif principal de l'utilisation de l'idiome Pimpl est de masquer les détails d'implémentation d'une classe. En laissant Derived_1::Impldériver Base::Impl, vous avez vaincu cet objectif. Maintenant, non seulement la mise en œuvre de Basedépend Base::Impl, la mise en œuvre de Derived_1dépend également de Base::Impl.
  Y a-t-il une meilleure solution?
Cela dépend des compromis que vous acceptez.
Solution 1
Rendez les Implclasses totalement indépendantes. Cela impliquera qu'il y aura deux pointeurs vers les Implclasses - un dans Baseet un autre dans Derived_N.
class Base {
   protected:
      Base() : pImpl{new Impl()} {}
   private:
      // It's own Impl class and pointer.
      class Impl { };
      std::shared_ptr<Impl> pImpl;
};
class Derived_1 final : public Base {
   public:
      Derived_1() : Base(), pImpl{new Impl()} {}
      void func_1() const;
   private:
      // It's own Impl class and pointer.
      class Impl { };
      std::shared_ptr<Impl> pImpl;
};
Solution 2
Exposez les classes uniquement en tant que descripteurs. N'exposez pas du tout les définitions de classe et les implémentations.
Fichier d'en-tête public:
struct Handle {unsigned long id;};
struct Derived1_tag {};
struct Derived2_tag {};
Handle constructObject(Derived1_tag tag);
Handle constructObject(Derived2_tag tag);
void deleteObject(Handle h);
void fun(Handle h, Derived1_tag tag);
void bar(Handle h, Derived2_tag tag); 
Voici une mise en œuvre rapide
#include <map>
class Base
{
   public:
      virtual ~Base() {}
};
class Derived1 : public Base
{
};
class Derived2 : public Base
{
};
namespace Base_Impl
{
   struct CompareHandle
   {
      bool operator()(Handle h1, Handle h2) const
      {
         return (h1.id < h2.id);
      }
   };
   using ObjectMap = std::map<Handle, Base*, CompareHandle>;
   ObjectMap& getObjectMap()
   {
      static ObjectMap theMap;
      return theMap;
   }
   unsigned long getNextID()
   {
      static unsigned id = 0;
      return ++id;
   }
   Handle getHandle(Base* obj)
   {
      auto id = getNextID();
      Handle h{id};
      getObjectMap()[h] = obj;
      return h;
   }
   Base* getObject(Handle h)
   {
      return getObjectMap()[h];
   }
   template <typename Der>
      Der* getObject(Handle h)
      {
         return dynamic_cast<Der*>(getObject(h));
      }
};
using namespace Base_Impl;
Handle constructObject(Derived1_tag tag)
{
   // Construct an object of type Derived1
   Derived1* obj = new Derived1;
   // Get a handle to the object and return it.
   return getHandle(obj);
}
Handle constructObject(Derived2_tag tag)
{
   // Construct an object of type Derived2
   Derived2* obj = new Derived2;
   // Get a handle to the object and return it.
   return getHandle(obj);
}
void deleteObject(Handle h)
{
   // Get a pointer to Base given the Handle.
   //
   Base* obj = getObject(h);
   // Remove it from the map.
   // Delete the object.
   if ( obj != nullptr )
   {
      getObjectMap().erase(h);
      delete obj;
   }
}
void fun(Handle h, Derived1_tag tag)
{
   // Get a pointer to Derived1 given the Handle.
   Derived1* obj = getObject<Derived1>(h);
   if ( obj == nullptr )
   {
      // Problem.
      // Decide how to deal with it.
      return;
   }
   // Use obj
}
void bar(Handle h, Derived2_tag tag)
{
   Derived2* obj = getObject<Derived2>(h);
   if ( obj == nullptr )
   {
      // Problem.
      // Decide how to deal with it.
      return;
   }
   // Use obj
}
Avantages et inconvénients
Avec la première approche, vous pouvez construire des Derivedclasses dans la pile. Avec la deuxième approche, ce n'est pas une option.
Avec la première approche, vous encourez le coût de deux allocations et désallocations dynamiques pour la construction et la destruction d'un Deriveddans la pile. Si vous construisez et détruisez un Derivedobjet à partir du tas vous, encourez le coût d'une allocation et d'une désallocation supplémentaires. Avec la deuxième approche, vous n'encourez que le coût d'une allocation dynamique et d'une désallocation pour chaque objet.
Avec la première approche, vous avez la possibilité d'utiliser la virtualfonction membre est Base. Avec la deuxième approche, ce n'est pas une option.
Ma suggestion
J'irais avec la première solution pour pouvoir utiliser la hiérarchie des classes et virtualles fonctions membres Basemême si c'est un peu plus cher.
               
              
Base, une classe de base abstraite normale ("interface") et des implémentations concrètes sans pimpl pourraient suffire.