[英]C-style cast of std::vector
當試圖找到將std::vector<Derived *>
轉換為std::vector<Base *>
的解決方案時,我在現有的代碼庫中偶然發現了此實現。 我正在使用C ++ 11。
考慮以下代碼片段:
#include <iostream>
#include <vector>
class A
{
// some implementation details
};
class B : public A
{
// some implementation details
};
void count(std::vector<A *> const & a_vec)
{
std::cout << "IT HAS THESE MANY PTRS: " << a_vec.size() << std::endl;
}
int main()
{
B * b;
std::vector<B *> b_vec {b};
count((std::vector<A *> &) b_vec);
return 0;
}
感覺很狡猾,所以我試圖找到一個替代方案。 這篇文章提出了一種使用std::vector::assign
。 所以現在,我的主要功能如下所示:
int main()
{
B * b;
std::vector<B *> b_vec {b};
std::vector<A *> new_vec;
new_vec.assign(b_vec.begin(), b_vec.end());
count(new_vec);
return 0;
}
它可以按預期進行編譯和工作。 現在我有以下問題:
1)為什么第一個代碼片段甚至可以編譯,但是使用static_cast
會導致編譯錯誤?
2)兩種方法的計算成本是多少? 由於創建臨時矢量對象new_vec
,我預計第二個會產生額外的費用,但我不確定。
3)在這種情況下,使用C樣式轉換有哪些弊端?
謝謝。
為什么第一個代碼段甚至可以編譯,但是使用static_cast會導致編譯錯誤?
因為C型投擲是一把大錘,將使一切警惕。 它的座右銘是“您想要它嗎?您得到了它”,無論它是什么。 靜態類型轉換只能執行在靜態類型檢查方面正確的類型轉換。
這兩種方法的計算成本是多少? 由於創建臨時矢量對象new_vec,我預計第二個會產生額外的費用,但是我不確定。
您的期望是正確的。 但是具有明確定義的語義的代碼成本可能會增加程序的工作量。
在這些情況下,使用C樣式轉換有哪些弊端?
它會一直編譯,並且直到將來嘗試在某個平台上運行它時,您才發現存在問題。 因為它今天可能會工作。
該代碼是胡說八道。 沒有要求,即a的值 Derived*
是相同的a的值 Base*
,因此告訴編譯器假裝一個std::vector<B*>
是一個std::vector<A*>
是不要求對任何明智的事情。 實際上,如果您有多個相同類型的鹼基,則該指針類型pun是不可能的。 試試吧:
#include <iostream>
struct Base {
int i;
};
struct I1 : Base {
int j;
};
struct I2 : Base {
int k;
};
struct Derived : I1, I2 {
int l;
};
int main() {
Derived d;
Base* b1 = &(I1&)d;
Base* b2 = &(I2&)d;
std::cout << (void*)&d << ' ' << (void*)b1 << ' ' << (void*)b2 << '\n';
return 0;
}
auto x = (Foo) someConstType
,您是要刪除const
限定詞還是那是偶然嗎?)。 在您的特定情況下,如果您具有多重繼承,則C樣式的版本將產生不正確的程序,並且向上轉換指針意味着需要更改其地址以指向適當的基類對象。
std::vector<Derived*>
是與std::vector<Base*>
不相關的類型。 沒有合法的方法可以將一個人的記憶解釋為另一個人的記憶,只是缺少像新安置這樣的瘋狂愚蠢的東西。
如果幸運的話,您的嘗試會產生錯誤。 如果不是這樣,它們會產生不確定的行為,這意味着它似乎在今天可以正常工作,但是明天,由於從編譯器升級,更遠的代碼更改或月球階段的各種變化,它們可以以無提示的方式格式化硬盤。
現在,情況是在vector<Base*>
上進行的許多操作都在vector<Derived*>
。 我們可以用類型擦除來解決這個問題。
這是一個低效率的類型擦除類:
template<class R, class...Args>
using vcfunc = std::function<R(void const*, Args...)>;
template<class T, class R, class...Args, class F>
vcfunc<R,Args...> vcimpl( F&& f ) {
return [f=std::forward<F>(f)](void const* pt, Args&&...args)->R{
return f( *static_cast<T const*>(pt), std::forward<Args>(args)... );
};
}
template<class T>
struct random_access_container_view {
using self=random_access_container_view;
struct vtable_t {
vcfunc<std::size_t> size;
vcfunc<bool> empty;
vcfunc<T, std::size_t> get;
};
vtable_t vtable;
void const* ptr = 0;
template<class C,
class dC=std::decay_t<C>,
std::enable_if_t<!std::is_same<dC, self>{}, int> =0
>
random_access_container_view( C&& c ):
vtable{
vcimpl<dC, std::size_t>( [](auto& c){ return c.size(); } ),
vcimpl<dC, bool>( [](auto& c){ return c.empty(); } ),
vcimpl<dC, T, std::size_t>( [](auto& c, std::size_t i){ return c[i]; } )
},
ptr( std::addressof(c) )
{}
std::size_t size() const { return vtable.size( ptr ); }
bool empty() const { return vtable.empty( ptr ); }
T operator[](std::size_t i) const { return vtable.get( ptr, i ); }
};
現在這有點玩具,因為它不支持迭代。 (迭代器大約和我上面寫的容器一樣復雜)。
現場例子 。
struct A {
char name='A';
};
struct B:A {
B(){ name='B'; }
};
void print_them( random_access_container_view<A> container ) {
for (std::size_t i = 0; i < container.size(); ++i ) {
std::cout << container[i].name << "\n";
}
}
int main() {
std::vector<B> bs(10);
print_them( bs );
}
允許將子容器視為基本列表的語言基本上會自動執行上述操作。 容器本身具有與虛擬功能表等效的功能,或者當您將容器視為基於虛擬功能表的視圖時,則由客戶端代碼合成和使用。
上面的代碼效率不是最高; 請注意,每個std::function
都是無狀態的。 我可以很輕松地用函數指針替換它們,並基於C
類型存儲一個靜態vtable來節省內存(但添加另一個間接尋址)。
我們也可以使用非視圖類型來簡化此操作,因為我們可以使用類型擦除模型概念模式代替此手動vtable模式。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.