使用矢量示例验证 C++ 对象的成员在运行时是否具有常量地址
verify that members of c++ objects have constant address during runtime with vector example
我试图说服自己 C++ 中的对象在其生命周期内具有不变的地址。这是一个最小的工作示例:
#include <iostream>
#include <type_traits>
#include <vector>
class Class1
{
public:
Class1(unsigned int * pt);
unsigned int * val_pt;
};
Class1::Class1(unsigned int * pt)
:
val_pt(pt)
{}
class Class2
{
public:
Class2(std::vector<unsigned int> vec_);
std::vector<unsigned int> vec_of_ints;
Class1 class1_instance;
};
Class2::Class2(std::vector<unsigned int> vec_)
:
vec_of_ints(vec_),
class1_instance(Class1(&vec_of_ints[0]))
{}
int main() {
std::vector<unsigned int> vec_test(10, 2);
Class2 instance_class2(vec_test);
Class1 instance_class1 = instance_class2.class1_instance;
//both addresses are equal
std::cout<<"Address stored in instance_class1: "<<instance_class1.val_pt<<" ,address of first vec_element of instance_class2: "<<&(instance_class2.vec_of_ints)[0]<<std::endl;
instance_class2.vec_of_ints.resize(20);
//different addresses now
std::cout<<"Address stored in instance_class1: "<<instance_class1.val_pt<<" ,address of first vec_element of instance_class2: "<<&(instance_class2.vec_of_ints)[0]<<std::endl;
return 0;
}
我的 Class2 存储了一个整数向量和一个 Class1 实例。 Class1存放的是Class2实例的vector的地址。
我想获取该向量的地址,即向量在堆栈中的存储地址。如果我的理解是正确的,则 resize() 函数不会更改堆栈上的地址,而只会更改该地址的内容,即向量指向堆中的位置。
我的总体目标是表明对 Class2 中向量的任何修改在 Class1 的存储指针中都是“可见的”。因此,如果我取消引用 Class1 中的指针,我将获得与在 Class2 中访问向量本身时相同的整数值。那是因为成员变量的地址在运行时是常量。
但我猜我的代码有问题,可能是在我传递 'vec[0]' 的构造函数中。我认为这不是堆栈中向量的实际地址,而是堆上的某个地址。我如何获得正确的地址?
欢迎任何意见!
你在这里做的事是不敬虔的,需要停止。考虑这个特别糟糕的模式:
class DataOwner {
public:
inline std::vector<uint32_t>& getData() { return data; }
private:
// I am safely tucked away
std::vector<uint32_t> data;
};
class I_Want_To_Work_On_Data {
public:
I_Want_To_Work_On_Data(DataOwner* owner) : owner(owner) {}
void doThing() {
auto& direct_ref_to_data = owner->getData();
for(auto& item : direct_ref_to_data) {
// This is just as fast as your direct pointer :/
}
}
private:
DataOwner* owner;
};
返回对数据的可变访问有些不好,但它比您采用的方法(至少在单线程环境中)安全得多。性能并不比您尝试的差,但它更安全。那么,您究竟要优化什么?您的方法如何改进这种无聊的模式?
现在你可能会争辩说,提供对 std::vector 的可变访问是不需要的(即不允许任何旧代码调整数组的大小),但这可以很容易地解决,而无需诉诸肮脏的黑客.
#include <vector>
#include <cstdint>
class DataOwner {
public:
inline std::vector<uint32_t>::iterator begin()
{ return data.begin(); }
inline std::vector<uint32_t>::iterator end()
{ return data.end(); }
inline std::vector<uint32_t>::const_iterator begin() const
{ return data.begin(); }
inline std::vector<uint32_t>::const_iterator end() const
{ return data.end(); }
private:
// I am safely tucked away
std::vector<uint32_t> data;
};
class ConstAccess {
public:
ConstAccess(const DataOwner& owner) : owner(owner) {}
void doThing() {
for(const auto& item : owner) {
}
}
private:
const DataOwner& owner;
};
class MutableAccess {
public:
MutableAccess(DataOwner& owner) : owner(owner) {}
void doThing() {
for(auto& item : owner) {
}
}
private:
DataOwner& owner;
};
性能与您的方法相同,但是这种方法具有以下优点:
- 在以下行的调试版本中不会崩溃:
class1_instance(Class1(&vec_of_ints[0]))
,当向量为空,并且您尝试取消引用 NULL 以查找地址时。
- 当您在不小心调整数组大小时尝试取消引用
unsigned int * val_pt;
时,它不会崩溃。
- 它不会让你不小心做:
delete [] val_pt
我不确定您从上面的评论和回复中得出了什么结论。
我只是想确定这些不在其中:
The address of a member variable is constant during runtime.
If, for example, you have a vector of Class2
instances, and you
resize that vector, the address of the vec_of_ints
member variable
may change for any of those instances.
- 在堆栈中有一个
Class2
实例或在堆中有一个指向 Class2
实例的指针会有所不同。
The address of the vec_of_ints
member variable shouldn't change if
you resize it, no matter the instance of Class2
is in the stack or
in the heap.
下面的示例测试了两个断言 (https://godbolt.org/z/3TYrnjro8):
#include <iomanip>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
struct HoldsIntVector
{
std::vector<int> v{};
};
int main()
{
HoldsIntVector stackInstance{};
auto heapInstance{std::make_unique<HoldsIntVector>()};
stackInstance.v.push_back(5);
heapInstance->v.push_back(5);
auto printStackAndHeapInstances = [&](const auto& text){
std::cout << std::showbase << std::hex;
std::cout << &stackInstance << "\t" << &heapInstance << "\t";
std::cout << &stackInstance.v << "\t" << &heapInstance->v << "\t";
std::cout << std::setw(8) << std::setfill('0') << stackInstance.v.data() << "\t\t";
std::cout << std::setw(8) << std::setfill('0') << heapInstance->v.data() << "\t\t";
std::cout << text;
std::cout << "\n";
};
std::cout << "stackInstance\theapInstance\tstackInstance.v\theapInstance.v\t&stackInstance.v[0]\t&heapInstance.v[0]\n";
printStackAndHeapInstances("after initializing stack and heap instances");
// After resizing both vectors in stack and heap instances
//
// Address of v doesn't change neither in stack nor in heap instances
// Address of v[0] changes in both stack and heap instances
stackInstance.v.resize(10);
heapInstance->v.resize(10);
printStackAndHeapInstances("after resizing both v's");
std::cout << "\n";
// Now what happens if we have a vector of HoldsIntVector and we resize it
//
// Address of v changes for the first HoldsInVector
std::vector<HoldsIntVector> hivs{10};
std::for_each(begin(hivs), end(hivs), [](auto& hiv){hiv.v.push_back(3);});
std::cout << "&hivs[0].v\n" << &hivs[0].v << "\t" << "after intializing hivs\n";
hivs.resize(20);
std::cout << &hivs[0].v << "\t" << "after resizing hivs\n";
}
我试图说服自己 C++ 中的对象在其生命周期内具有不变的地址。这是一个最小的工作示例:
#include <iostream>
#include <type_traits>
#include <vector>
class Class1
{
public:
Class1(unsigned int * pt);
unsigned int * val_pt;
};
Class1::Class1(unsigned int * pt)
:
val_pt(pt)
{}
class Class2
{
public:
Class2(std::vector<unsigned int> vec_);
std::vector<unsigned int> vec_of_ints;
Class1 class1_instance;
};
Class2::Class2(std::vector<unsigned int> vec_)
:
vec_of_ints(vec_),
class1_instance(Class1(&vec_of_ints[0]))
{}
int main() {
std::vector<unsigned int> vec_test(10, 2);
Class2 instance_class2(vec_test);
Class1 instance_class1 = instance_class2.class1_instance;
//both addresses are equal
std::cout<<"Address stored in instance_class1: "<<instance_class1.val_pt<<" ,address of first vec_element of instance_class2: "<<&(instance_class2.vec_of_ints)[0]<<std::endl;
instance_class2.vec_of_ints.resize(20);
//different addresses now
std::cout<<"Address stored in instance_class1: "<<instance_class1.val_pt<<" ,address of first vec_element of instance_class2: "<<&(instance_class2.vec_of_ints)[0]<<std::endl;
return 0;
}
我的 Class2 存储了一个整数向量和一个 Class1 实例。 Class1存放的是Class2实例的vector的地址。
我想获取该向量的地址,即向量在堆栈中的存储地址。如果我的理解是正确的,则 resize() 函数不会更改堆栈上的地址,而只会更改该地址的内容,即向量指向堆中的位置。 我的总体目标是表明对 Class2 中向量的任何修改在 Class1 的存储指针中都是“可见的”。因此,如果我取消引用 Class1 中的指针,我将获得与在 Class2 中访问向量本身时相同的整数值。那是因为成员变量的地址在运行时是常量。
但我猜我的代码有问题,可能是在我传递 'vec[0]' 的构造函数中。我认为这不是堆栈中向量的实际地址,而是堆上的某个地址。我如何获得正确的地址?
欢迎任何意见!
你在这里做的事是不敬虔的,需要停止。考虑这个特别糟糕的模式:
class DataOwner {
public:
inline std::vector<uint32_t>& getData() { return data; }
private:
// I am safely tucked away
std::vector<uint32_t> data;
};
class I_Want_To_Work_On_Data {
public:
I_Want_To_Work_On_Data(DataOwner* owner) : owner(owner) {}
void doThing() {
auto& direct_ref_to_data = owner->getData();
for(auto& item : direct_ref_to_data) {
// This is just as fast as your direct pointer :/
}
}
private:
DataOwner* owner;
};
返回对数据的可变访问有些不好,但它比您采用的方法(至少在单线程环境中)安全得多。性能并不比您尝试的差,但它更安全。那么,您究竟要优化什么?您的方法如何改进这种无聊的模式?
现在你可能会争辩说,提供对 std::vector 的可变访问是不需要的(即不允许任何旧代码调整数组的大小),但这可以很容易地解决,而无需诉诸肮脏的黑客.
#include <vector>
#include <cstdint>
class DataOwner {
public:
inline std::vector<uint32_t>::iterator begin()
{ return data.begin(); }
inline std::vector<uint32_t>::iterator end()
{ return data.end(); }
inline std::vector<uint32_t>::const_iterator begin() const
{ return data.begin(); }
inline std::vector<uint32_t>::const_iterator end() const
{ return data.end(); }
private:
// I am safely tucked away
std::vector<uint32_t> data;
};
class ConstAccess {
public:
ConstAccess(const DataOwner& owner) : owner(owner) {}
void doThing() {
for(const auto& item : owner) {
}
}
private:
const DataOwner& owner;
};
class MutableAccess {
public:
MutableAccess(DataOwner& owner) : owner(owner) {}
void doThing() {
for(auto& item : owner) {
}
}
private:
DataOwner& owner;
};
性能与您的方法相同,但是这种方法具有以下优点:
- 在以下行的调试版本中不会崩溃:
class1_instance(Class1(&vec_of_ints[0]))
,当向量为空,并且您尝试取消引用 NULL 以查找地址时。 - 当您在不小心调整数组大小时尝试取消引用
unsigned int * val_pt;
时,它不会崩溃。 - 它不会让你不小心做:
delete [] val_pt
我不确定您从上面的评论和回复中得出了什么结论。
我只是想确定这些不在其中:
The address of a member variable is constant during runtime.
If, for example, you have a vector of
Class2
instances, and you resize that vector, the address of thevec_of_ints
member variable may change for any of those instances.
- 在堆栈中有一个
Class2
实例或在堆中有一个指向Class2
实例的指针会有所不同。
The address of the
vec_of_ints
member variable shouldn't change if you resize it, no matter the instance ofClass2
is in the stack or in the heap.
下面的示例测试了两个断言 (https://godbolt.org/z/3TYrnjro8):
#include <iomanip>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
struct HoldsIntVector
{
std::vector<int> v{};
};
int main()
{
HoldsIntVector stackInstance{};
auto heapInstance{std::make_unique<HoldsIntVector>()};
stackInstance.v.push_back(5);
heapInstance->v.push_back(5);
auto printStackAndHeapInstances = [&](const auto& text){
std::cout << std::showbase << std::hex;
std::cout << &stackInstance << "\t" << &heapInstance << "\t";
std::cout << &stackInstance.v << "\t" << &heapInstance->v << "\t";
std::cout << std::setw(8) << std::setfill('0') << stackInstance.v.data() << "\t\t";
std::cout << std::setw(8) << std::setfill('0') << heapInstance->v.data() << "\t\t";
std::cout << text;
std::cout << "\n";
};
std::cout << "stackInstance\theapInstance\tstackInstance.v\theapInstance.v\t&stackInstance.v[0]\t&heapInstance.v[0]\n";
printStackAndHeapInstances("after initializing stack and heap instances");
// After resizing both vectors in stack and heap instances
//
// Address of v doesn't change neither in stack nor in heap instances
// Address of v[0] changes in both stack and heap instances
stackInstance.v.resize(10);
heapInstance->v.resize(10);
printStackAndHeapInstances("after resizing both v's");
std::cout << "\n";
// Now what happens if we have a vector of HoldsIntVector and we resize it
//
// Address of v changes for the first HoldsInVector
std::vector<HoldsIntVector> hivs{10};
std::for_each(begin(hivs), end(hivs), [](auto& hiv){hiv.v.push_back(3);});
std::cout << "&hivs[0].v\n" << &hivs[0].v << "\t" << "after intializing hivs\n";
hivs.resize(20);
std::cout << &hivs[0].v << "\t" << "after resizing hivs\n";
}