一、C++标准库提供的是value语义

  • 通常,所有容器都会建立元素拷贝,返回的也是元素的拷贝
  • STL只支持value语义,不支持reference语义。优缺点如下:
  • 优点:
  • 复制元素很简单
  • 使用reference时容易出错,你必须确保reference所指对象仍然健在,并需要小心队服偶尔出现的环式指向状态
  • 缺点:
  • 复制元素可能会导致不良的效率,有时甚至无法复制
  • 无法在数个不同的容器中管理同一份对象
  • 实现reference语义的两种做法:
  • ①使用智能指针
  • ②使用reference_wrapper

二、使用Shared Pointer

  • C++标准库提供了许多智能指针。如果你打算在不同的容器之间共享对象,shared_ptr<>是一个适当的选择


演示案例

  • 创建了一个Item类和一个printItems()函数用来打印类的内容

class Item {
private:
std::string name;
float price;
public:
Item(const std::string& n, float p = 0) :name(n), price(p) {}

std::string getName()const { return name; }
void setName(const std::string& n) { name = n; }

float getPrice()const { return price; }
void setPrice(float p) { price = p; }
};

template<typename Coll>
void printItems(const std::string& msg, const Coll& coll)
{
std::cout << msg << std::endl;
for (const auto& elem : coll)
{
std::cout << " " << elem->getName() << ":" << elem->getPrice() << std::endl;
}
}

  • 测试程序如下:

int main()
{
typedef std::shared_ptr<Item> ItemPtr;
std::set<ItemPtr> allItems;
std::deque<ItemPtr> bestsellers;

//为bestsellers添加元素
bestsellers = { ItemPtr(new Item("Kong Yize",20.10)),
ItemPtr(new Item("A Midsummer Night's Dream",14.99)),
ItemPtr(new Item("The Maltese Falcon",9.88))
};
//为allItems添加元素
allItems = { ItemPtr(new Item("Water",0.44)),
ItemPtr(new Item("Pizza",2.22))
};
//将bestsellers内的所有元素添加进allItems
allItems.insert(bestsellers.begin(), bestsellers.end());

//打印一下bestsellers和allItems内的元素
printItems("bestsellers", bestsellers);
printItems("allItems", allItems);
std::cout << std::endl;

//遍历bestsellers,将其内部的每一个对象的price加倍
for_each(bestsellers.begin(), bestsellers.end(),
[](std::shared_ptr<Item>& elem) {elem->setPrice(elem->getPrice() * 2);}
);
//将bestsellers[1]的元素更改为allItems内名为“Pizza”的元素
bestsellers[1] = *(
find_if(allItems.begin(), allItems.end(),
[](std::shared_ptr<Item> elem) {return elem->getName() == "Pizza"; })
);
//将bestsellers[0]对象的price设置为44.77
bestsellers[0]->setPrice(44.77);

//打印一下bestsellers和allItems内的元素
printItems("bestsellers", bestsellers);
printItems("allItems", allItems);
}

  • 使用智能指针为set<>和deque<>创建元素,其内的每个元素都是智能指针。后面对set<>和deque<>进行了一系列的操作,由于智能指针是共享的,因此改变一者,另一者也改变

C++(标准库):25---STL容器之(实现容器reference语义)_c++

  • 注意事项:使用shared_ptr<>会使事情变得复杂。例如,set<>容器内存储的是智能指针之后,不能再使用find()算法了,因此find()算法会找出拥有相等value的元素,现在比较的却是内部(由new返回)的pointer,因此只能使用find_if()算法


三、使用Reference Wrapper

  • reference_wrapper在之前的文章介绍过,参阅:​
  • reference_wrapper可以将传值调用改为传reference调用
  • 假设保证“只要容器存在,被指向的元素一定存在”,那就可以使用reference_wrapper。见下面的演示案例


演示案例

  • 此演示案例仍然使用到上面的Item类

int main()
{
std::vector<std::reference_wrapper<Item>> books;

Item f("Faust", 12.99);
books.push_back(f); //将f的引入传递给books,现在books内存放的是f的引用

for (const auto& book : books) {
std::cout << book.get().getName() << ": " << book.get().getPrice() << std::endl;
}

//通过f改变内容
f.setPrice(9.99);
std::cout << books[0].get().getPrice() << std::endl;

//可以看到vector内的也改变了
for (const auto& book : books) {
std::cout << book.get().getName() << ": " << book.get().getPrice() << std::endl;
}
}

C++(标准库):25---STL容器之(实现容器reference语义)_c++_02

  • 这种做法的优点是不需要pointer语法。然而这个也有风险,因此此处使用reference而非显然意见
  • 注意,下面的声明是不行的,因此其不是引用:

C++(标准库):25---STL容器之(实现容器reference语义)_值调用_03