C++如何在原地转换向量类型

3

不用创建新的数据结构,是否有可能实现这个功能呢? 假设我们有

struct Span{
    int from;
    int to;
}
vector<Span> s;

我们希望通过转换,直接从s中获取一个整数向量。
vector<Span> s;

为了

vector<int> s;

因此,我们可以删除/更改一些“from”、“to”元素,然后将其转换回。

vector<Span> s;  

7
你的描述不太合理。这听起来像是一个XY问题。你到底想做什么?能给出一个例子吗? - R. Martinho Fernandes
很难猜测你想要什么。你能提供一个例子吗? - MAK
@R.MartinhoFernandes:我认为发帖者想要将一个vector<something>转换成一个vector<other>,然后从这个vector<other>中获取一些other,然后再将其转换回vector<something>。但我不认为这是可能的。我非常确定,在Java中,这将是一个非常错误的操作,而且我不认为你可以在C++中做到这一点。 - blahman
+1 因为这是一个有趣的问题,即使它可能是个坏主意。 - Jon Purdy
2
@Polymorpher:首先,你应该问“我有这个算法来做某些事情,但它会复制。我该如何原地操作?”其次,“vector”不是一个复杂的数据结构。第三,你为什么认为你需要“黑客攻击”什么东西才能做到这一点? - R. Martinho Fernandes
显示剩余2条评论
3个回答

2
这不是一个真正好的想法,但我会告诉你怎么做。
你可以通过以下方式获取指向整数的原始指针:
int * myPointer2 = (int*)&(s[0]);

但这真的是一种不好的做法,因为您无法保证 span 结构没有任何 padding,所以尽管它今天对我和你可能正常工作,但我们不能对其他系统说太多。
#include <iostream>
#include <vector>


struct Span{
    int from;
    int to;
};


int main()
{

    std::vector<Span> s;

    Span a = { 1, 2};
    Span b = {2, 9};
    Span c = {10, 14};

    s.push_back(a);
    s.push_back(b);
    s.push_back(c);


    int * myPointer = (int*)&(s[0]);

    for(int k = 0; k < 6; k++)
    {
        std::cout << myPointer[k] << std::endl;
    }

    return 0;
}

正文:
如我所说,那个艰难的reinterpret_cast通常可以工作,但非常危险,并且缺乏C/C++通常期望的跨平台保证。
接下来更糟糕的是这个,它实际上会做你要求的事情,但你永远不应该这样做。这是你可能因为这种代码而被解雇的类型:
// Baaaad mojo here: turn a vector<span> into a vector<int>:
std::vector<int> * pis = (std::vector<int>*)&s;

for ( std::vector<int>::iterator It = pis->begin(); It != pis->end(); It++ )
        std::cout << *It << std::endl;

注意我如何使用指向vector的指针并指向向量对象s的地址。我的希望是两个向量的内部是相同的,我可以像那样使用它们。对我来说,这很有效,尽管标准模板可能会幸运地要求这种情况发生,但对于模板类来说通常不是这样(请参见填充和模板特化等内容)。
考虑改为复制一个数组(请参见下面的ref 2),或者只使用s.from和s[2].to。
相关阅读:
  1. std::vector元素是否保证连续?
  2. 如何在C++中将vector转换为数组

+1:我在我的 VBox 中尝试了一下,但我没有想到要将其转换为指针,然后再转回来。这种技巧似乎更像是你会在 C 中找到/黑出来的东西,正如你所指出的那样,它真的非常危险。一个非常好的答案^^ - blahman
1
我认为一个“更好”的想法是将其转换为指向2个元素数组的指针:reinterpret_cast<int(*)[2]>(&s[0]);,然后使用下标运算符访问各个成员。但是,我认为这仍然是可怕的未定义行为。 - Xeo
不必要使用丑陋的强制转换。将第一行替换为int * myPointer2 = &(s[0].from);。在实践中,它将在任何sizeof (Span) = 2 * sizeof (int)的系统上工作,因为这使得所有指针数学计算结果相同。 - Ben Voigt
谢谢。非常好的解释。这几乎是我正在寻找的 - 一个脏技巧来黑掉C++ STL并使其工作。唯一的问题是,这个黑客不会自动更正向量的内部值/成员(如果在将向量转换为vector<int>后插入/删除了一些元素),因此我们可能会在以后遇到一些问题。复制向量将使代码变慢太多。我想最好的方法仍然是创建一个新的数据结构 - 自定义动态数组。 - aaronqli
@Steve 删除不会在代码的其他部分引起太多速度问题(删除仅会稍后调用一次)。列表不允许随机访问,因此对于二分搜索来说是不好的。我认为转换的主要问题是向量本身的成员 - 迭代器、大小、容量等 - 不会自动更改(也许在STL内部实现了?我稍后会进行测试)。因此,在对int向量执行某些操作并将其转换回span向量后,一切都会混乱。我将查看STL向量的实现,以查看是否可以修复它。 - aaronqli
显示剩余2条评论

2
如果sizeof(Span) == sizeof(int)* 2(也就是说,Span 没有填充),那么您可以安全地使用reinterpret_cast<int*>(&v[0])来获取指向int数组的指针,以便您可以迭代它。您可以通过在GCC中使用__attribute__((__packed__))和在Visual Studio中使用#pragma pack来保证每个编译器上没有填充的结构。
然而,还有一种方法是标准所保证的。像这样定义Span:
struct Span {
    int endpoints[2];
};
endpoints[0]endpoints[1]需要连续。如果您愿意,可以添加一些方便的from()to()访问器,但现在您可以尽情使用reinterpret_cast<int*>(&v[0])
但是,如果您要频繁进行这种指针操作,您可能需要创建自己的类似于vector的数据结构,更易于进行此处理,提供更多安全保证,以避免出错。

1
免责声明:我对你试图做的事情一无所知。我只是根据我的经验和知识提供可能的解决方案。希望我能猜对一个,这样你就不必使用愚蠢的转换来进行疯狂的折腾了。
如果你想从向量中删除某个元素,你只需要找到它并使用erase函数将其删除即可。你需要一个指向该元素的迭代器,而获取该迭代器取决于你对该元素的了解。给定std::vector<Span> v;
  • 如果您知道它的索引:

    v.erase(v.begin() + idx);
    
  • 如果您有一个与要查找的对象相等的对象:

    Span doppelganger;
    v.erase(std::find(v.begin(), v.end(), doppelganger));
    
  • 如果您有一个与您要查找的对象相等但想要删除所有相同元素的对象,则需要使用擦除-移除习惯用法:

    Span doppelganger;
    v.erase(std::remove(v.begin(), v.end(), doppelganger)),
            v.end());
    
  • 如果您有一些标准来选择元素:

    v.erase(std::find(v.begin(), v.end(),
                      [](Span const& s) { return s.from == 0; }));
    
    // 在 C++03 中,您需要一个单独的函数来确定标准
    bool starts_from_zero(Span const& s) { return s.from == 0; }
    
    v.erase(std::find(v.begin(), v.end(), starts_from_zero));
    
  • 如果您有一些标准并且想要删除所有符合该标准的元素,则再次需要使用擦除-移除习惯用法:

    v.erase(std::remove_if(v.begin(), v.end(), starts_from_zero)),
            v.end());
    

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接