我有以下模板類:
// A.hpp
class A_data {
public:
int foo = 1;
virtual ~A_data() = default;
};
template <class C = A_data> class A {
public:
int foo() const; // Not implemented here
A() { d = new C; }
~A() { delete d; }
C* d;
};
我實作的默認實體A:
// A.cpp
#include "A.hpp"
// Template source implementation
template <> int A<A_data>::foo() const { return d->foo; }
現在,我可以將類與默認實體一起使用:
// main.cpp
#include <iostream>
#include "A.hpp"
int main() {
A a;
std::cout << a.foo(); // Prints 1
}
但是,假設我想創建一個繼承的類A,以及它自己的繼承資料類A_data,如下所示:
// A.hpp
class B_data : public A_data {
public:
int bar = 2;
virtual ~B_data() = default;
};
// Template here is in case something needs to inherit B, like in A.
template <class C = B_data> class B : public A<C> {
public:
int bar() const; // Not implemented here, just like A::foo.
};
// A.cpp
template <> int B<B_data>::bar() const { return d->bar; }
這會導致以下問題:
#include <iostream>
#include "A.hpp"
int main() {
B b;
std::cout << b.bar(); // Works fine, prints 2
std::cout << b.foo(); // Linker error: no implementation of A<B_data>::foo!
}
正如您可能從示例中了解到的那樣,目的是讓一個類具有自己的資料容器(在本例中為Aand A_data),以便其他類可以繼承它(B),并且可以選擇通過繼承來擴展資料容器父容器 ( B_data)。
我可以通過在 中定義實作來解決這個問題A.hpp,但在這種情況下,我不想提供A除A_data及其派生類之外的多個介面。如果我真的想,我可以在 中實作每個實體A.cpp,但它沒有增加任何價值,因為實作本身是不變的。
我的期望是B<B_data>能夠“了解”A<A_data>和使用A <A_data>::foo,因為B繼承自A. 顯然,因為B<B_data>和B<A_data>不相關,所以情況并非如此——但可以嗎?
我可以在不暴露標題中的實作的情況下實作這個“擴展類”介面嗎?如果沒有,是否有更好的選擇仍然可以讓我維護這個簡單的界面?
與常規類繼承相比,模板在這里似乎很有吸引力的部分原因是它們允許自動取消參考正確的容器資料,而無需進行任何型別的指標轉??換。常規課程示例:
#include "A.hpp"
int A::foo() const { return d->foo; }
int B::bar() const { return d->bar; } // Compiler error: No member 'bar' in 'A_data'
uj5u.com熱心網友回復:
如果您想堅持使用模板,您可以按如下方式實作此“鏡像繼承”:make B<B_data>inherit fromA<A_data>而不是A<B_data>通過在(或其他可能的模板引數型別)中宣告您想要考慮的任何型別B_data的別名來實作您的目的(base一般來說,它是模棱兩可的,因為一個類可能有很多基類)并且B<C>繼承自A<C::base>. 然后,對于B<C>, 因為d是 型別C::base*,它仍然可以保存一個C派生自的指標C::base(假設C::base設定充分并且是一個實際的基數)。但是在這種情況下,我們需要提供一個自定義的默認建構式,B以便它為 構造C物件d,而不是C::base如果默認建構式 forA<C::base>將被隱式默認的默認建構式呼叫,就會發生這種情況B<C>(請注意,不需要提供解構式,因為delete dfromA<C::base>的解構式將在C::base具有虛擬解構式時正常作業)。然后,因為在內部B<C>我們知道我們是如何構造物件d指向的,所以我們可以安全地假設我們知道它的動態型別并通過static_cast-ed 指標訪問它(為了安全起見,您可以dynamic_cast在內部添加 -checkassert以進行除錯構建檢查)。
示例實作(另請參閱注釋,特別是代碼其他方面的一些更改):
// A.hpp
class A_data {
public:
int foo = 1;
virtual ~A_data() = default;
};
template <class C = A_data> class A {
public:
int foo() const;
A() { d = new C; }
// 'virtual' is not strictly needed in our particular case since we don't delete B
// through A*, but still it is safer and best practice with it than without
virtual ~A() { delete d; }
C* d;
};
class B_data : public A_data {
public:
using base = A_data;
int bar = 2;
// virtual ~B_data() = default; implicitly defaulted and virtual
};
template <class C = B_data> class B : public A<typename C::base> {
public:
B() { this->d = new C; } // 'this' needed to trigger dependent name lookup
int bar() const;
};
// A.cpp
#include "A.hpp"
template <> int A<A_data>::foo() const { return d->foo; }
template <> int B<B_data>::bar() const { return static_cast<B_data*>(d)->bar; }
// main.cpp
#include <iostream>
#include "A.hpp"
int main() {
B b;
std::cout << b.bar();
std::cout << b.foo();
}
另請注意,如果您想使用不同的模板引數C,您可能希望避免在A.cpp的特化定義中出現代碼重復。在那里,您通常可以定義您的模板化函式(不是作為特化),但要強制實體化,請使用顯式模板實體化。例如,假設有B_data_2這樣定義的類:
// in A.hpp
class B_data_2 : public A_data {
public:
using base = A_data;
int bar = 3;
};
它還具有bar公共資料成員,但具有不同的默認初始化程式。現在我們可以為 A.cpp 添加另一個B<B_data_2>::bar()特化,但它與 基本相同B<B_data>::bar(),因此會出現代碼重復:
// in A.cpp
template <> int A<A_data>::foo() const { return d->foo; }
template <> int B<B_data>::bar() const { return static_cast<B_data*>(d)->bar; }
template <> int B<B_data_2>::bar() const { return static_cast<B_data_2*>(d)->bar; }
相反,如果我們用通用定義替換特化,然后是方法的顯式實體化定義,我們有:
// in A.cpp
template<class C> int A<C>::foo() const { return d->foo; }
template int A<A_data>::foo() const;
template<class C> int B<C>::bar() const {
return static_cast<C*>(this->d)->bar; // Again 'this' to enable dependent name lookup
}
template int B<B_data>::bar() const;
template int B<B_data_2>::bar() const;
這顯然可以更好地擴展,但是為了避免對同一特化的每個成員函式進行顯式實體化,我們可以做得更好 - 顯式實體化整個類特化,特別是自動實體化該類的所有(非模板化)成員函式專長:
// in A.cpp
template<class C> int A<C>::foo() const { return d->foo; }
template<class C> int B<C>::bar() const {
return static_cast<C*>(this->d)->bar; // Yet again 'this' to enable dependent name lookup
}
template class A<A_data>;
template class B<B_data>;
template class B<B_data_2>;
現在我們可以測驗并查看它是否可以編譯和作業:
// main.cpp
#include <iostream>
#include "A.hpp"
int main() {
B b;
B<B_data_2> b_2;
std::cout << b.bar() << b.foo() << std::endl; // 21
std::cout << b_2.bar() << b_2.foo() << std::endl; // 31
}
uj5u.com熱心網友回復:
在您的情況下,將實作放在標題中會很好,并且您不需要專門化:
class A_data {
public:
int foo = 1;
virtual ~A_data() = default;
};
template <class C = A_data>
requires(requires(C c) { c.foo; } ) // Possibly, in C 20
class A
{
public:
int foo() const { return d->foo; }
A() = default;
std::unique_ptr<C> d = std::make_unique<C>();
};
class B_data : public A_data {
public:
int bar = 2;
virtual ~B_data() = default;
};
// Template here is in case something needs to inherit B, like in A.
template <class C = B_data>
requires(requires(C c) { c.foo; c.bar;} ) // Possibly, in C 20
class B : public A<C> {
public:
int bar() const { return this->d->bar; }
};
#include <iostream>
int main() {
B b;
std::cout << b.bar();
std::cout << b.foo();
}
演示
uj5u.com熱心網友回復:
問題是您已經明確專門foo針對模板引數而A_data不是B_data.
要解決這個問題,只需將顯式特化更改為引數B_data,如下所示:
A.cpp
#include "A.hpp"
//----------------vvvvvv---------------------------------->changed from A_data to B_data
template <> int A<B_data>::foo() const { return d->foo; }
template <> int B<B_data>::bar() const { return d->bar; }
主檔案
#include <iostream>
#include "A.hpp"
int main() {
B b;
std::cout << b.bar(); // Works fine, prints 2
std::cout << b.foo(); // WORKS FINE, prints 1
}
作業演示
上述修改程式的輸出為:
21
解釋
B被實體化,B_data因此它繼承自A<B_data>. 請注意,它繼承自A<B_data>而不是A<A_data>。這意味著應該提供foo' 的實作。A<B_data>但是您從未提供foo' 的實作,A<B_data>因為您實際提供的是foofor的實作A<A_data>,因此出現了錯誤。因此,通過更改A_data為B_data如上面修改后的代碼所示,我們擺脫了錯誤。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/493252.html
