与其他人所说的使用 std::reference_wrapper<T> 类似但不同,这可能有用,但有人在您的问题下面的 cmets 中也提到了这一点,那就是使用智能指针,这里唯一的区别是我碰巧采取了通过创建模板包装类更进一步。这是代码,它应该做你正在寻找的东西,除了这是在堆上工作而不是使用引用。
#include <iostream>
#include <memory>
#include <string>
#include <vector>
class Car {
public:
std::string color;
std::string name;
Car(){} // Added Default Constructor to be safe.
Car( std::string colorIn, std::string nameIn ) : color( colorIn ), name( nameIn ){}
};
template<class T>
class Wrapper {
public:
std::shared_ptr<T> ptr;
explicit Wrapper( T obj ) {
ptr = std::make_shared<T>( T( obj ) );
}
~Wrapper() {
ptr.reset();
}
};
int main () {
std::vector<Wrapper<Car>> collection1;
std::vector<Wrapper<Car>> collection2;
collection1.emplace_back( Car("black", "Ford") );
collection1.emplace_back( Car("white", "BMW") );
collection1.emplace_back( Car("yellow", "Audi") );
collection2.push_back( collection1[0] );
std::cout << collection2[0].ptr->color << " " << collection2[0].ptr->name << std::endl;
collection2[0].ptr->color = std::string( "green" );
collection2[0].ptr->name = std::string( "Gremlin" );
std::cout << collection1[0].ptr->color << " " << collection1[0].ptr->name << std::endl;
return 0;
}
如果您在代码中注意到我更改了集合 2 的第一个索引对象的字段,然后我打印了集合 1 的第一个索引对象的字段并且它们被更改了。因此,在一个集合中发生的事情将在另一个集合中发生,因为它们是 shared memory 使用 std::shared_ptr<T>每次都这样做;模板包装类会为您执行此操作,您不必担心清理内存,因为std::shared_ptr<T>'s destructor 应该为您执行此操作,但为了安全起见,我确实在Wrapper's destructor 中调用了shared_ptr<T>'s release method。
为了使它更简洁或更具可读性,您可以这样做:
typedef Wrapper<Car> car;
std::vector<car> collection1;
std::vector<car> collection2;
// rest is same
它也会为你做同样的事情。
现在,如果您不想使用指针或堆,您可以自己创建另一个类似于std::refrence_wrapper<T> 的包装器,您可以为引用编写自己的模板包装器,使用起来非常简单。这是一个例子:
template<class T>
class Wrapper2 {
public:
T& t;
explicit Wrapper2( T& obj ) : t(obj) {}
};
然后在您的来源中,您将执行与上述相同的操作,它仍然有效
typedef Wrapper2<Car> car2;
std::vector<car2> coll1;
std::vector<car2> coll2;
coll1.emplace_back( Car( "black", "Ford" ) );
coll1.emplace_back( Car( "white", "BMW" ) );
coll1.emplace_back( Car( "yellow", "Audi" ) );
coll2.push_back( coll1[0] );
std::cout << coll2[0].t.color << " " << coll2[0].t.name << std::endl;
coll2[0].t.color = std::string( "brown" );
coll2[0].t.name = std::string( "Nova" );
std::cout << coll1[0].t.color << " " << coll1[0].t.name << std::endl;
通过修改 coll2 的第一个索引对象的字段,coll1 的第一个索引对象的字段也被更改。
编辑
@Caleth 在 cmets 中问过我这个问题:
Wrapper 与这里的 shared_ptr 相比有什么好处? (以及 Wrapper2 在 reference_wrapper 上)
如果没有这个包装,请看这里的代码:
class Blob {
public:
int blah;
Blob() : blah(0) {}
explicit Blob( int blahIn ) : blah( blahIn ) {}
};
void someFunc( ... ) {
std::vector<std::shared_ptr<Blob>> blobs;
blobs.push_back( std::make_shared<Blob>( Blob( 1 ) ) );
blobs.push_back( std::make_shared<Blob>( Blob( 2 ) ) );
blobs.push_back( std::make_shared<Blob>( Blob( 3 ) ) );
}
是的,它是可读的,但有很多重复的输入,现在有了包装器
void someFunc( ... ) {
typedef Wrapper<Blob> blob;
std::vector<blob> blobs;
blobs.push_back( Blob( 1 ) );
blobs.push_back( Blob( 2 ) );
blobs.push_back( Blob( 3 ) );
}
现在对于 Wrapper 来说只是一个参考;尝试这样做:
void someFunc( ... ) {
std::vector<int&> ints; // Won't Work
}
但是,创建一个将 reference 存储到 obj T 的 class template,您现在可以这样做:
void someFunc( ... ) {
typedef Wrapper2<Blob> blob;
std::vector<blob> blobs;
blobs.push_back( Blob( 1 ) );
blobs.push_back( Blob( 2 ) );
// then lets create a second container
std::vector<blob> blobs2;
// Push one of the reference objects in container 1 into container two
blobs2.push_back( blobs[0] );
// Now blobs2[0] contains the same referenced object as blobs[0]
// blobs[0].t.blah = 1, blobs[1].t.blah = 2 and blobs2[0].t.blah = 1
// lets change blobs2[0].t.blah value
blobs2[0].t.blah = 4;
// Now blobs1[0].t.blah also = 4.
}
在std::vector<T> 中的引用之前无法做到这一点,除非你使用std::reference_wrapper<T>,它的作用基本相同,但更复杂。所以对于简单的对象,拥有自己的包装器可以派上用场。
编辑 - 在我的 IDE 中工作时我忽略了并且没有注意到这一点,因为所有内容都已成功编译、构建和运行,但我注意到这个问题的 OP 应该完全无视我的第二个包装。这可能导致未定义的行为。因此,您仍然可以使用智能指针的第一个包装器,或者如果您需要其他人已经指出的可存储引用,请务必使用std::some_container<std::reference_wrapper<T>>。我将上面的现有代码留作历史参考,供其他人学习。我非常感谢那些指出未定义行为的人。对于那些不知道的人,请考虑到我没有接受过正规培训,而且我是 100% 自学的并且还在学习。您也可以在这里参考我就引用和未定义行为提出的这个问题:undefined behavior of references on stack
结论
尝试在多个容器中使用相同对象的引用可能不是一个好主意,因为当从任一容器中添加或删除某些内容时会导致未定义行为,从而留下悬空引用。因此,正确或更安全的选择是使用std::shared_ptr<T> 来实现您想要的功能。
使用引用并没有错,但需要特别注意和设计,尤其是被引用对象的生命周期。如果对象被移动,然后引用被访问,这将导致问题,但如果您知道对象的生命周期并且它不会被移动或销毁,那么访问引用就不是问题。我仍然建议使用std::shared_ptr 或std::reference_wrapper