C++ std::pair / std :: tuple / std::optional 的用法

leetcode —— Two Sum 

刷题刷到std::pair和make_pair()函数,记录下用法,做一个?上人。

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>

using namespace std;

class Solution
{
    // 新思路,两根指针分别指向vector首部以及尾部
    // 左侧指针向右移,右侧指针向左移
    // 两指针指向的内存数据相加与target比较
    // 左侧指针只在相加结果<target时右移,
    // 右侧指针只在相加结果>target时左移。
    // 左侧指针与右侧指针相遇时结束。

public:
vector<int> twoSum(vector<int> &nums, int target)
{
	vector<int> ret;
	// 对nums排序并保存下标
	std::vector< std::pair<int, int> > nums_;

	int nums_size = nums.size();
	for (int i = 0; i < nums_size; ++i) { nums_.push_back(make_pair(nums[i], i)); }
	std::sort(nums_.begin(), nums_.end());

	// 下标声明
	int l = 0, r = nums_.size() - 1;
	while (l < r)
	{
		if (nums_[l].first + nums_[r].first == target)
		{
			ret.push_back(nums_[l].second);
			ret.push_back(nums_[r].second);
			break;
		}
		else if (nums_[l].first + nums_[r].first > target)
		{
			r--;
		}
		else l++;
	}

	return ret;
}

};

int main()
{
	//int arr[100] = {0};
	//cout << arr[0] << " " << arr[1]<<" "<<arr[100];
	Solution m_sol;
	vector<int> nums{ 2,7,11,15 };
	vector<int> m_return;
	m_return=m_sol.twoSum(nums , 13);
	for (auto i : m_return)
		cout << i << " ";

	return 0;
}

使用vector配合std::pair成为简易的unordered_map;

 

背景

C/C++ 语言中的函数只能返回一个值。那么我们如何达到返回多个值的目的?

  1. 使用指针指向一个容纳多个非对称类型的值的结构,将指针返回到该数据类型的数组。
  2. 使用元组tuple(用于返回多个值)和成对pair(用于两个值)。

 

std::pair

1 pair的应用

pair是将2个数据组合成一个数据,当需要这样的需求时就可以使用pair,如stl中的map就是将key和value放在一起来保存。另一个应用是,当一个函数需要返回2个数据的时候,可以选择pair。 pair的实现是一个结构体,主要的两个成员变量是first second 因为是使用struct不是class,所以可以直接使用pair的成员变量。

2 make_pair函数

template pair make_pair(T1 a, T2 b) { return pair(a, b); }

很明显,我们可以使用pair的构造函数也可以使用make_pair来生成我们需要的pair。 一般make_pair都使用在需要pair做参数的位置,可以直接调用make_pair生成pair对象很方便,代码也很清晰。 另一个使用的方面就是pair可以接受隐式的类型转换,这样可以获得更高的灵活度。灵活度也带来了一些问题如:

std::pair<int, float>(1, 1.1);

std::make_pair(1, 1.1);

是不同的,第一个就是float,而第2个会自己匹配成double。

类模板:template <class T1, class T2> struct pair

参数:T1是第一个值的数据类型,T2是第二个值的数据类型。

功能:pair将一对值组合成一个值,这一对值可以具有不同的数据类型(T1和T2),两个值可以分别用pair的两个公有函数first和second访问。

具体用法:

1.定义(构造):

1     pair<int, double> p1;  //使用默认构造函数
2     pair<int, double> p2(1, 2.4);  //用给定值初始化
3     pair<int, double> p3(p2);  //拷贝构造函数

2.访问两个元素(通过first和second):

1     pair<int, double> p1;  //使用默认构造函数
2     p1.first = 1;
3     p1.second = 2.5;
4     cout << p1.first << ' ' << p1.second << endl;

输出结果:1 2.5

 

std::tuple

pair 可以理解为一种特殊的 tuple(只有 2 个元素的 tuple),tuple是一个固定大小的不同类型值的集合,是泛化的std::pair。我们也可以把他当做一个通用的结构体来用,不需要创建结构体又获取结构体的特征,在某些情况下可以取代结构体使程序更简洁,直观。std::tuple理论上可以有无数个任意类型的成员变量,而std::pair只能是2个成员,因此在需要保存3个及以上的数据时就需要使用tuple元组了。

#include<iostream>
#include<bits/stdc++.h> 
using namespace std; 
  
// A Method that returns multiple values using 
// tuple in C++. 
tuple<int, int, char> foo(int n1, int n2) 
{ 
    // Packing values to return a tuple 
    return make_tuple(n2, n1, 'a');              
} 
  
// A Method returns a pair of values using pair 
std::pair<int, int> foo1(int num1, int num2) 
{ 
    // Packing two values to return a pair  
    return std::make_pair(num2, num1);             
} 
  
int main() 
{ 
    int a,b; 
    char cc; 
      
    // Unpack the elements returned by foo 
    std::tie(a, b, cc) = foo(5, 10);              //创建到其参数或 std::ignore 实例的左值引用的 tuple 。  
      
    // Storing  returned values in a pair  
    std::pair<int, int> p = foo1(5,2);   
      
    cout << "Values returned by tuple: "; 
    cout << a << " " << b << " " << cc << endl; 
      
    cout << "Values returned by Pair: "; 
    cout << p.first << " " << p.second; 
    return 0; 
} 

 

std::tie

std::tie 能用于引入字典序比较到结构体,或解包 tuple 。通过tie解包将会把这4个元素的值分别赋值给tie提供的4个变量中。

#include <iostream>
#include <string>
#include <set>
#include <tuple>
 
struct S {
    int n;
    std::string s;
    float d;
    bool operator<(const S& rhs) const
    {
        // 比较 n 与 rhs.n,
        // 然后为 s 与 rhs.s,
        // 然后为 d 与 rhs.d
        return std::tie(n, s, d) < std::tie(rhs.n, rhs.s, rhs.d);
    }
};
 
int main()
{
    std::set<S> set_of_s; // S 为可比较小于 (LessThanComparable)
 
    S value{42, "Test", 3.14};
    std::set<S>::iterator iter;
    bool inserted;
 
    // 解包 insert 的返回值为 iter 与 inserted
    std::tie(iter, inserted) = set_of_s.insert(value);
 
    if (inserted)
        std::cout << "Value was inserted successfully\n";
}

 

std::optional

std::optional 是在 C++ 17 中引入到标准库中的,C++ 17 之前的版本可以通过 boost::optional 实现几乎相同的功能。

The class template std::optional manages an optional contained value, i.e. a value that may or may not be present.         
A common use case for optional is the return value of a function that may fail. As opposed to other approaches, such as std::pair<T,bool>, optional handles expensive-to-construct objects well and is more readable, as the intent is expressed explicitly.

类模板 std::optional 管理一个可选的容纳值,即可以存在也可以不存在的值。

一种常见的 optional 使用情况是一个可能失败的函数的返回值。与其他手段,如 std::pair<T,bool> 相比, optional 良好地处理构造开销高昂的对象,并更加可读,因为它显式表达意图。

若一个 optional<T> 含值,则保证值作为 optional 对象所用空间的一部分分配,即不会发生动态内存分配。从而 optional 对象模拟一个对象,而非指针,尽管定义了 operator*() 和 operator->() 运算符。

当一个 optional<T> 对象被隐式转换成 bool时,若对象含值则转换返回 true ,若对象不含值则返回 false 。

optional 对象在下列条件下含值:

  • 对象被以 T 类型值或另一含值的 optional 初始化/赋值。

对象在下列条件下不含值:

  • 对象被默认初始化。
  • 对象被以 std::nullopt_t 类型值( 等同于{ } )或不含值的 optional 对象初始化/赋值。
  • 调用了成员函数 reset() 。

如果程序optional使用引用类型实例化一个程序,则格式不正确。另外,类型optionalstd :: reference_wrapperT可以用来保存引用。

此外,如果程序optional使用(可能是cv限定的)标记类型std :: nullopt_tstd :: in_place_t实例化一个程序,则该程序格式将报错。

创建一个 optional 的方法:

// 空 optiolal
optional<int> oEmpty;
optional<float> oFloat = nullopt;

optional<int> oInt(10);
optional oIntDeduced(10);  // type deduction

// make_optional
auto oDouble = std::make_optional(3.0);
auto oComplex = make_optional<complex<double>>(3.0, 4.0);

// in_place
optional<complex<double>> o7{in_place, 3.0, 4.0};

// initializer list
optional<vector<int>> oVec(in_place, {1, 2, 3});

// 拷贝赋值
auto oIntCopy = oInt;

示例:

#include <iostream>
#include <string>
#include <functional>
#include <optional>

struct Out {
	std::string out1{ "" };
	std::string out2{ "" };
};

std::optional<Out> func(const std::string& in) {
	Out o;
	if (in.size() == 0)
		return std::nullopt;
	o.out1 = "hello";
	o.out2 = "world";
	return { o };
}

// optional can be used as the return type of a factory that may fail
std::optional<std::string> create(bool b) {
	if (b)
		return "Godzilla";
	return {};
}

// std::nullopt can be used to create any (empty) std::optional
auto create2(bool b) {
	return b ? std::optional<std::string>{"Godzilla"} : std::nullopt;
}

// std::reference_wrapper may be used to return a reference
auto create_ref(bool b) {
	static std::string value = "Godzilla";
	return b ? std::optional<std::reference_wrapper<std::string>>{value}: std::nullopt;
}

int main()
{
	if (auto ret = func("hi"); ret.has_value()) {
		std::cout << ret->out1 << std::endl;
		std::cout << ret->out2 << std::endl;
	}

	std::cout << "create(false) returned "
		<< create(false).value_or("empty") << '\n';

	// optional-returning factory functions are usable as conditions of while and if
	if (std::optional<std::string> str = create2(true)) {
		std::cout << "create2(true) returned " << *str << '\n';
	}

	if (auto str = create_ref(true)) {
		// using get() to access the reference_wrapper's value
		std::cout << "create_ref(true) returned " << str->get() << '\n';

		str->get() = "Mothra";
		std::cout << "modifying it changed it to " << str->get() << '\n';
	}
}

使用 std::optional 带来的好处:

  • 省去了运行状态的 bool 值的声明,让代码更简洁,更注重返回值本身的语意
  • 不用担心额外的动态内存分配,这一点会在后面的文章里详细展开

 

 

参考文章:

In file included from D:/SelfUsing/Language/CLion 2025.1.3/mingw/lib/gcc/x86_64-w64-mingw32/14.2.0/include/c++/bits/hashtable_policy.h:34, from D:/SelfUsing/Language/CLion 2025.1.3/mingw/lib/gcc/x86_64-w64-mingw32/14.2.0/include/c++/bits/hashtable.h:35, from D:/SelfUsing/Language/CLion 2025.1.3/mingw/lib/gcc/x86_64-w64-mingw32/14.2.0/include/c++/bits/unordered_map.h:33, from D:/SelfUsing/Language/CLion 2025.1.3/mingw/lib/gcc/x86_64-w64-mingw32/14.2.0/include/c++/unordered_map:41, from D:/obj/i wanna own world to me/i wanna own world to me.cpp:9: D:/SelfUsing/Language/CLion 2025.1.3/mingw/lib/gcc/x86_64-w64-mingw32/14.2.0/include/c++/tuple: In instantiation of 'constexpr std::pair<_T1, _T2>::pair(std::tuple<_Args1 ...>&, std::tuple<_Args2 ...>&, std::_Index_tuple<_Indexes1 ...>, std::_Index_tuple<_Indexes2 ...>) [with _Args1 = {std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&&}; long long unsigned int ..._Indexes1 = {0}; _Args2 = {}; long long unsigned int ..._Indexes2 = {}; _T1 = const std::__cxx11::basic_string<char>; _T2 = SDL_GL_Image]': D:/SelfUsing/Language/CLion 2025.1.3/mingw/lib/gcc/x86_64-w64-mingw32/14.2.0/include/c++/tuple:2877:63: required from 'constexpr std::pair<_T1, _T2>::pair(std::piecewise_construct_t, std::tuple<_Args1 ...>, std::tuple<_Args2 ...>) [with _Args1 = {std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&&}; _Args2 = {}; _T1 = const std::__cxx11::basic_string<char>; _T2 = SDL_GL_Image]' 2877 | typename _Build_index_tuple<sizeof...(_Args2)>::__type()) | ^ D:/SelfUsing/Language/CLion 2025.1.3/mingw/lib/gcc/x86_64-w64-mingw32/14.2.0/include/c++/bits/stl_construct.h:97:14: required from 'constexpr decltype (::new(void*(0)) _Tp) std::construct_at(_Tp*, _Args&& ...) [with _Tp = pair<const __cxx11::basic_string<char>, SDL_GL_Image>; _Args = {const piecewise_construct_t&, tuple<__cxx11::basic_string<char, char_traits<char>, allocator<char> >&&>, tuple<>}; decltype (::new(void*(0)) _Tp) = pair<const __cxx11::basic_string<char>, SDL_GL_Image>*]' 97 | { return ::new((void*)__location) _Tp(std::forward<_Args>(__args)...); } | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ D:/SelfUsing/Language/CLion 2025.1.3/mingw/lib/gcc/x86_64-w64-mingw32/14.2.0/include/c++/bits/alloc_traits.h:536:21: required from 'static constexpr void std::allocator_traits<std::allocator<_Up> >::construct(allocator_type&, _Up*, _Args&& ...) [with _Up = std::pair<const std::__cxx11::basic_string<char>, SDL_GL_Image>; _Args = {const std::piecewise_construct_t&, std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&&>, std::tuple<>}; _Tp = std::__detail::_Hash_node<std::pair<const std::__cxx11::basic_string<char>, SDL_GL_Image>, true>; allocator_type = std::allocator<std::__detail::_Hash_node<std::pair<const std::__cxx11::basic_string<char>, SDL_GL_Image>, true> >]' 536 | std::construct_at(__p, std::forward<_Args>(__args)...); | ~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ D:/SelfUsing/Language/CLion 2025.1.3/mingw/lib/gcc/x86_64-w64-mingw32/14.2.0/include/c++/bits/hashtable_policy.h:2024:36: required from 'std::__detail::_Hashtable_alloc<_NodeAlloc>::__node_type* std::__detail::_Hashtable_alloc<_NodeAlloc>::_M_allocate_node(_Args&& ...) [with _Args = {const std::piecewise_construct_t&, std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&&>, std::tuple<>}; _NodeAlloc = std::allocator<std::__detail::_Hash_node<std::pair<const std::__cxx11::basic_string<char>, SDL_GL_Image>, true> >; __node_ptr = std::allocator<std::__detail::_Hash_node<std::pair<const std::__cxx11::basic_string<char>, SDL_GL_Image>, true> >::value_type*]' 2024 | __node_alloc_traits::construct(__alloc, __n->_M_valptr(), | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~ 2025 | std::forward<_Args>(__args)...); | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ D:/SelfUsing/Language/CLion 2025.1.3/mingw/lib/gcc/x86_64-w64-mingw32/14.2.0/include/c++/bits/hashtable.h:312:35: required from 'std::_Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal, _Hash, _RangeHash, _Unused, _RehashPolicy, _Traits>::_Scoped_node::_Scoped_node(std::_Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal, _Hash, _RangeHash, _Unused, _RehashPolicy, _Traits>::__hashtable_alloc*, _Args&& ...) [with _Args = {const std::piecewise_construct_t&, std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&&>, std::tuple<>}; _Key = std::__cxx11::basic_string<char>; _Value = std::pair<const std::__cxx11::basic_string<char>, SDL_GL_Image>; _Alloc = std::allocator<std::pair<const std::__cxx11::basic_string<char>, SDL_GL_Image> >; _ExtractKey = std::__detail::_Select1st; _Equal = std::equal_to<std::__cxx11::basic_string<char> >; _Hash = std::hash<std::__cxx11::basic_string<char> >; _RangeHash = std::__detail::_Mod_range_hashing; _Unused = std::__detail::_Default_ranged_hash; _RehashPolicy = std::__detail::_Prime_rehash_policy; _Traits = std::__detail::_Hashtable_traits<true, false, true>; std::_Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal, _Hash, _RangeHash, _Unused, _RehashPolicy, _Traits>::__hashtable_alloc = std::_Hashtable<std::__cxx11::basic_string<char>, std::pair<const std::__cxx11::basic_string<char>, SDL_GL_Image>, std::allocator<std::pair<const std::__cxx11::basic_string<char>, SDL_GL_Image> >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char> >, std::hash<std::__cxx11::basic_string<char> >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >::__hashtable_alloc]' 312 | _M_node(__h->_M_allocate_node(std::forward<_Args>(__args)...)) | ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ D:/SelfUsing/Language/CLion 2025.1.3/mingw/lib/gcc/x86_64-w64-mingw32/14.2.0/include/c++/bits/hashtable_policy.h:870:42: required from 'std::__detail::_Map_base<_Key, std::pair<const _Key, _Val>, _Alloc, std::__detail::_Select1st, _Equal, _Hash, _RangeHash, _Unused, _RehashPolicy, _Traits, true>::mapped_type& std::__detail::_Map_base<_Key, std::pair<const _Key, _Val>, _Alloc, std::__detail::_Select1st, _Equal, _Hash, _RangeHash, _Unused, _RehashPolicy, _Traits, true>::operator[](key_type&&) [with _Key = std::__cxx11::basic_string<char>; _Val = SDL_GL_Image; _Alloc = std::allocator<std::pair<const std::__cxx11::basic_string<char>, SDL_GL_Image> >; _Equal = std::equal_to<std::__cxx11::basic_string<char> >; _Hash = std::hash<std::__cxx11::basic_string<char> >; _RangeHash = std::__detail::_Mod_range_hashing; _Unused = std::__detail::_Default_ranged_hash; _RehashPolicy = std::__detail::_Prime_rehash_policy; _Traits = std::__detail::_Hashtable_traits<true, false, true>; mapped_type = SDL_GL_Image; key_type = std::__cxx11::basic_string<char>]' 870 | typename __hashtable::_Scoped_node __node { | ^~~~~~ D:/SelfUsing/Language/CLion 2025.1.3/mingw/lib/gcc/x86_64-w64-mingw32/14.2.0/include/c++/bits/unordered_map.h:992:20: required from 'std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::mapped_type& std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::operator[](key_type&&) [with _Key = std::__cxx11::basic_string<char>; _Tp = SDL_GL_Image; _Hash = std::hash<std::__cxx11::basic_string<char> >; _Pred = std::equal_to<std::__cxx11::basic_string<char> >; _Alloc = std::allocator<std::pair<const std::__cxx11::basic_string<char>, SDL_GL_Image> >; mapped_type = SDL_GL_Image; key_type = std::__cxx11::basic_string<char>]' 992 | { return _M_h[std::move(__k)]; } | ~~~~^ D:/obj/i wanna own world to me/i wanna own world to me.cpp:164:57: required from here 164 | glBindTexture(GL_TEXTURE_2D, images["sprBrownBlock"s].textureID); | ^ D:/SelfUsing/Language/CLion 2025.1.3/mingw/lib/gcc/x86_64-w64-mingw32/14.2.0/include/c++/tuple:2888:9: error: no matching function for call to 'SDL_GL_Image::SDL_GL_Image()' 2888 | second(std::forward<_Args2>(std::get<_Indexes2>(__tuple2))...) | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ D:/obj/i wanna own world to me/i wanna own world to me.cpp:43:14: note: candidate: 'SDL_GL_Image::SDL_GL_Image(const char*)' 43 | explicit SDL_GL_Image(const char* path) { | ^~~~~~~~~~~~ D:/obj/i wanna own world to me/i wanna own world to me.cpp:43:14: note: candidate expects 1 argument, 0 provided D:/obj/i wanna own world to me/i wanna own world to me.cpp:39:7: note: candidate: 'constexpr SDL_GL_Image::SDL_GL_Image(const SDL_GL_Image&)' 39 | union SDL_GL_Image { | ^~~~~~~~~~~~ D:/obj/i wanna own world to me/i wanna own world to me.cpp:39:7: note: candidate expects 1 argument, 0 provided ninja: build stopped: subcommand failed.
最新发布
08-05
<think>我们正在讨论的是在C++中使用`unordered_map`插入`SDL_GL_Image`对象时遇到的错误,错误信息是“default constructor missing”。这意味着在尝试插入元素时,`unordered_map`需要调用`SDL_GL_Image`的默认构造函数,但该构造函数不存在。 ### 问题分析 `std::unordered_map`的`insert`方法在插入元素时,有几种方式,其中一种是通过`std::pair`来插入键值对。但有时,`unordered_map`可能会尝试使用值类型的默认构造函数来创建临时对象,然后再进行复制或移动。如果值类型(这里是`SDL_GL_Image`)没有默认构造函数,就会导致编译错误。 具体来说,当我们使用`unordered_map::insert`的其中一种形式,例如: ```cpp std::unordered_map<Key, T> myMap; myMap.insert(std::make_pair(key, T())); // 这里需要T有默认构造函数 ``` 或者在某些情况下,`unordered_map`内部可能会尝试构造一个默认初始化的值对象。 ### 解决方案 1. **避免需要默认构造函数**:使用`unordered_map`的`emplace`方法或`insert`方法中不需要默认构造函数的版本。 - `emplace`方法:直接在容器内构造元素,避免临时对象的创建。这样,我们可以使用`SDL_GL_Image`的任意构造函数。 ```cpp myMap.emplace(key, arg1, arg2, ...); // 传递构造SDL_GL_Image所需的参数 ``` 或者,如果`SDL_GL_Image`支持移动构造,也可以这样: ```cpp myMap.emplace(key, SDL_GL_Image(...)); // 使用参数构造一个临时对象,然后移动 ``` 2. **使用`insert`的正确形式**:如果一定要用`insert`,可以传递一个已经构造好的键值对,这样就不会调用默认构造函数。 ```cpp myMap.insert({key, SDL_GL_Image(arg1, arg2, ...)}); ``` 3. **检查`SDL_GL_Image`的构造函数**:确保你有一个合适的构造函数来创建对象。如果`SDL_GL_Image`没有默认构造函数,那么你就不能无参构造它,必须提供参数。 ### 示例代码 假设`SDL_GL_Image`有一个构造函数,它接受一个文件路径字符串作为参数: ```cpp class SDL_GL_Image { public: SDL_GL_Image(const std::string& filepath); // 构造函数,需要文件路径 // ... 没有默认构造函数 }; ``` 那么,在`unordered_map`中插入元素可以这样: ```cpp #include <unordered_map> #include <string> int main() { std::unordered_map<std::string, SDL_GL_Image> imageMap; // 使用emplace imageMap.emplace("texture1", "path/to/image1.png"); // 或者使用insert imageMap.insert(std::make_pair("texture2", SDL_GL_Image("path/to/image2.png"))); return 0; } ``` ### 注意事项 - 如果`SDL_GL_Image`的拷贝构造函数被删除(例如,因为资源管理需要移动语义),那么你可能需要使用`emplace`来直接构造,或者使用移动语义(如`std::move`)。 - 如果`SDL_GL_Image`对象很大,考虑使用指针(如`std::unique_ptr`)存储在`unordered_map`中,以避免拷贝开销,同时也能解决默认构造问题(因为指针有默认构造)。但这样会增加一层间接访问。 ### 总结 错误“default constructor missing”是因为`unordered_map`的某些操作需要值类型具有默认构造函数。解决方法是通过`emplace`或带参数的`insert`来插入元素,避免调用默认构造函数。同时,确保提供正确的参数来调用`SDL_GL_Image`的构造函数。 如果问题仍然存在,请检查`SDL_GL_Image`的构造函数是否可用,以及是否所有必要的参数都已提供。 §§相关问题§§ 1. C++中`emplace`和`insert`有什么区别? 2. 如何为自定义类实现移动语义? 3. 在`unordered_map`中存储大型对象的最佳实践是什么? 4. 如何避免`unordered_map`插入时的拷贝开销?</think>### 解决C++ `unordered_map`插入`SDL_GL_Image`时缺少默认构造函数的错误 当您在C++中使用`std::unordered_map`插入`SDL_GL_Image`对象时遇到"default constructor missing"错误,这是因为`unordered_map`的某些操作需要值类型具有默认构造函数。以下是详细分析和解决方案: #### 问题原因分析 1. **`unordered_map`的底层机制**: - `unordered_map`在插入元素时(如`insert()`或`operator[]`),可能需要先构造一个值类型的默认初始化对象 - 当您尝试访问不存在的键时(如`map[key]`),容器会自动插入一个默认构造的值对象 - 如果值类型`SDL_GL_Image`没有默认构造函数,就会触发编译错误 2. **SDL_GL_Image的限制**: - SDL图像类通常禁用默认构造函数,因为它们需要特定资源初始化 - 错误示例代码: ```cpp std::unordered_map<std::string, SDL_GL_Image> textureMap; textureMap["player"]; // 需要默认构造SDL_GL_Image → 错误! ``` #### 解决方案 ##### 方法1:使用`emplace()`直接构造(推荐) ```cpp std::unordered_map<std::string, SDL_GL_Image> textureMap; // 使用emplace直接传递构造参数 textureMap.emplace("player", "textures/player.png", SDL_IMAGE_FORMAT_PNG); // 或者使用try_emplace(C++17+) textureMap.try_emplace("enemy", "textures/enemy.jpg", SDL_IMAGE_FORMAT_JPEG); ``` - ✅ 优点:避免临时对象构造,直接使用参数创建对象 - ✅ 无需默认构造函数 ##### 方法2:使用指针存储 ```cpp std::unordered_map<std::string, std::unique_ptr<SDL_GL_Image>> textureMap; // 插入时直接构造对象 textureMap["player"] = std::make_unique<SDL_GL_Image>("textures/player.png"); // 访问时使用指针语法 if (textureMap.find("player") != textureMap.end()) { textureMap["player"]->Render(); // 使用->操作符 } ``` - ✅ 优点:避免对象拷贝,支持多态 - ✅ 指针类型有默认构造函数(nullptr) ##### 方法3:自定义默认构造行为 如果必须使用值语义,可通过包装类实现: ```cpp struct TextureWrapper { std::optional<SDL_GL_Image> texture; TextureWrapper() = default; // 默认构造合法 // 延迟初始化 void Init(const std::string& path) { texture.emplace(path, SDL_IMAGE_FORMAT_AUTO); } }; std::unordered_map<std::string, TextureWrapper> textureMap; textureMap["player"].Init("textures/player.png"); ``` #### 关键注意事项 1. **避免使用`operator[]`进行查找**: ```cpp // 错误:可能触发默认构造 auto& tex = textureMap["missing_texture"]; // 正确:使用find() if (auto it = textureMap.find("player"); it != textureMap.end()) { it->second.Render(); } ``` 2. **插入前检查键是否存在**: ```cpp if (textureMap.count("new_texture") == 0) { textureMap.emplace("new_texture", "path/to/texture.png"); } ``` 3. **C++版本影响**: - C++17+:优先使用`try_emplace()`,性能更好且不要求可默认构造 - C++11:使用`emplace()`或指针方案 #### 完整示例 ```cpp #include <SDL_image.h> #include <unordered_map> #include <memory> int main() { std::unordered_map<std::string, std::unique_ptr<SDL_GL_Image>> textures; // 安全插入 textures.emplace("background", std::make_unique<SDL_GL_Image>("bg.png", SDL_IMAGE_FORMAT_PNG)); // 安全访问 if (auto it = textures.find("background"); it != textures.end()) { it->second->Render(); } // 添加新纹理(避免operator[]) textures.emplace("character", std::make_unique<SDL_GL_Image>("char.png", SDL_IMAGE_FORMAT_PNG)); return 0; } ``` 通过上述方法,您可以绕过SDL_GL_Image缺少默认构造函数的问题,同时保持代码的安全性和高效性。选择方案时优先考虑`emplace`/`try_emplace`或智能指针方案,它们能最好地满足资源管理需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值