指向矢量元素的持久指针代替"this"

persistent pointer to vector element in place of 'this'

本文关键字:指针 this 元素      更新时间:2023-10-16

我需要指向std::vector元素的持久指针。直接的this不行,如图所示:

#include <functional>
#include <vector>
#include <iostream>
class A {
public:
A() = delete;
A(int i)
: my_i(i)
, whoAmI([this]()->void{ std::cout<<"I am class A with i="<<my_i<<std::endl; })
{}
A(const A&) = delete;
A(A&&) = default;
int my_i;
std::function<void()> whoAmI;
};
int main()
{
std::vector<A> vec;
std::cout << "Initialization:" << std::endl;
for (int i=0; i<3; ++i) {
vec.push_back(A(i));
vec.back().whoAmI();
}
std::cout << "Retrieval:" << std::endl;
for (int i=0; i<3; ++i)
vec.at(i).whoAmI();
}

产生

Initialization:
I am class A with i=0
I am class A with i=1
I am class A with i=2
Retrieval:
I am class A with i=2
I am class A with i=2
I am class A with i=2

而我需要

Retrieval:
I am class A with i=0
I am class A with i=1
I am class A with i=2

当然,在这个例子中,人们可以很容易地绕过它。在现实生活中,持久指针应传递给Qt::connect

也许可以尝试按如下值捕获索引:

#include <functional>
#include <vector>
#include <iostream>

class A {
public:
A() = delete;
A(int i, std::vector<A> &vec)
: whoAmI([i, &vec]()->void{
std::cout<<"I am class A with i="<< i << ". Calling whoAmI(): "  << std::endl;
vec[i];
})
{}
A(const A&) = delete;
A(A&&) = default;
std::function<void()> whoAmI;

};
int main()
{
std::vector<A> vec;
std::cout << "Initialization:" << std::endl;
for (int i=0; i<3; ++i) {
vec.push_back(A(i, vec));
vec.back().whoAmI();
}
std::cout << "Retrieval:" << std::endl;
for (int i=0; i<3; ++i)
vec.at(i).whoAmI();
}

您在lambda的inizalization站点上绑定了它,因此在任何情况下都无法实现对象的移动或复制。

一个可能的解决方案是添加一个间接级别:

#include <functional>
#include <vector>
#include <iostream>
class A;
class fuction_wrapper
{
public:
A* ref;
std::function<void(A*)> lambda;
void operator()() const { lambda(ref); }
};
class A {
public:
A() = delete;
A(int i)
: my_i(i)
, whoAmI({this, [] (A* a) { std::cout<<"I am class A with i="<< a->my_i << std::endl; }})
{}
A(const A&) = delete;
A(A&& a)
{
my_i = std::move(a.my_i);
whoAmI = { this, std::move(a.whoAmI.lambda) };
}
int my_i;
fuction_wrapper whoAmI;
};
int main()
{
std::vector<A> vec;
std::cout << "Initialization:" << std::endl;
for (int i=0; i<3; ++i) {
vec.push_back(A(i));
vec.back().whoAmI();
}
std::cout << "Retrieval:" << std::endl;
for (int i=0; i<3; ++i)
vec.at(i).whoAmI();
}

可能不是最能养眼的解决方案,但它确实有效。

您需要在move构造函数中重新定义lambda,以便它在移动后获得更新的this,因为您推入的临时A(i)已经不存在了。

A(A&& a) 
{ 
this->my_i = a.my_i; 
this->whoAmI = [this]()->void { std::cout << "I am class A with i=" << this->my_i << std::endl; }; 
};

https://ideone.com/s5ZyS5

Initialization:
I am class A with i=0
I am class A with i=1
I am class A with i=2
Retrieval:
I am class A with i=0
I am class A with i=1
I am class A with i=2

如果不涉及移动构造函数A(A&&),就无法设置std::vector<A>的元素。移动后,旧的this正在悬挂。让我们称之为移动这个问题。

此外,矢量元素地址可能会随着矢量的增长而变化。这个问题可以通过保留足够的向量大小来解决,也可以通过将向量元素封装在唯一指针中来解决。然而,这些措施都没有解决这个问题,因此这是更根本的问题。

移动这个问题有几个解决方案:

  • 不使用this,而是捕获拥有的向量和向量索引(请参阅@bartop的解决方案(

  • 更新move构造函数中的lambda表达式(请参阅@Jack和@KillzoneKid的解决方案(

  • 插入一个单独的init函数:,而不是在常规构造函数中初始化lambda表达式,并在move构造函数中更新它

class A {
...
A(int i) : my_i(i) {}
/* A(A&&) = default; */
void init();
...
};
void A::init() {
whoAmI = [this]()->void{ std::cout<<"I am class A with i="<<my_i<<std::endl; };
}
int main()
{
std::vector<A> vec;
std::cout << "Initialization:" << std::endl;
for (int i=0; i<3; ++i) {
vec.push_back(A(i));
vec.back().init();
vec.back().whoAmI();
}
...
}

然而,在对我的QObject::connect应用程序进行了几天的实验之后,我得出的结论是,通过将相关逻辑移动到父类(在这里显示的最小示例中:移动到main(来避免整个问题要好得多。