使用矢量示例验证 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;
};

性能与您的方法相同,但是这种方法具有以下优点:

  1. 在以下行的调试版本中不会崩溃:class1_instance(Class1(&vec_of_ints[0])),当向量为空,并且您尝试取消引用 NULL 以查找地址时。
  2. 当您在不小心调整数组大小时尝试取消引用 unsigned int * val_pt; 时,它不会崩溃。
  3. 它不会让你不小心做:delete [] val_pt

我不确定您从上面的评论和回复中得出了什么结论。

我只是想确定这些不在其中:

  1. 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.

  1. 在堆栈中有一个 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";
}