简体   繁体   中英

Returning std::vector in std::map

I'm trying to return an std::vector that is inside a map but I don't seem to get it right.

The code below is what I have tried and the one further below is the test it's supposed to pass

namespace grade_school {
class school {
    public:
        inline std::map<int, std::vector<std::string>> roster () {return 
MySchool;} 
        void add (std::string studentname , int grd);
        std::vector<std::string>& grade (int grd);
    private: 
        std::map<int,std::vector<std::string>> MySchool;
      };
}


void grade_school::school::add (std::string studentname, int grd){
MySchool[grd].push_back(studentname);
}

std::vector<std::string>& grade (int grd) {
return MySchool[grd];
};

TEST_CASE("grade_returns_the_students_in_that_grade_in_alphabetical_order")
{
grade_school::school school_;
school_.add("Franklin", 5);
school_.add("Bradley", 5);
school_.add("Jeff", 1);

const auto actual = school_.grade(5);

const vector<string> expected{"Bradley", "Franklin"};
REQUIRE(expected == actual);

}

I expected that the return type would be the vector contained inside the map but the compiler error i got was error C2065: 'MySchool': undeclared identifier

I expected that the return type would be the vector contained inside the map.

This leaves room for interpretation, depending on how you interpret this sentence, this already is the case – or not...

std::vector<std::string> grade(int grd);

In C++, we speak of returning 'by value'. You get exactly the values that are in the map. But returning by value means, you get a copy of. If you now modify the returned value, you actually modify the copy, but not the vector in the map.

std::vector<std::string>& grade(int grd);
//                      ^ note the ampersand!

The ampersand denotes that we just return a reference to the vector in the map (in this case, it behaves similar to a pointer), and via the reference, you now can modify the vector in the map directly:

grade(7).push_back("seven");

For completeness: pointer:

std::vector<std::string>* grade(int grd) { return &mySchool[grd]; }
// need address-of operator:                      ^

grade(7)->push_back("seven");

Usually, one prefers the reference, but the pointer can be useful in some specific scenarios, eg you can return a nullptr (not possible with references) to denote that there is no suitable vector for some specific input.

In some use-cases, you might not want to allow to modify the vector returned. Returning by copy avoids that, but a copy might be expensive. To avoid copies and still disallow modifications, you can return by const reference:

std::vector<std::string> const& grade(int grd);

Admitted, a user might const_cast the const away, but then it's the user breaking a contract (returning something as const means tealling: 'Don't modify it, if you do so, your fault!').

Use reference type for return value:

 std::map<int,std::vector<std::string>> MySchool;
 std::vector<std::string>& grade (int grd) {
   return MySchool[grd];
 }

Using reference with containers and other enough big data objects is preferable due to perfomance. This minimize memory operations caused by data duplications.

Non const reference approach is good if you plan to modify vector outside of method or object which incapsulate this map. This is simple solution which always give you actual data and allow to modify.

Better approach is to incapsulate modifications of data in methods of class (or api function set) to avoid unexpected and unverified modificaions by user. Use return type const reference for that like this:

const std::vector<std::string>& grade (int grd)

If you really need to have a copy of array for that case you can do this simple by expression sintax. For example assign to value instead of reference:

// ref - is const reference to some array returned by function
const std::vector<std::string>& ref = grade(10)

// array - is a full copy of array pointed by reference returned from function
std::vector<std::string> array = grade(10)

When you return reference to container or some data you need to understand problem that user code save reference and it may be used multiple times. And during that time and dependent from how your container or your code manage data in memory it may bring to problem. So it is possible case when data at memory referenced by prevously returned reference is released. Returned before reference become refer to wrong memory. But user code may use that reference and bring to access violation or some undefined behaviour.

Even if your object guarantee that reference returned by method will exists during all object life time. Reference may be saved in the user code more long time than object itself. So there is no guarantee at all.

However we may use simple rule just to not save a reference in variable. Just use data in same expression with function call always:

  grade(10).length()
  std::accumulate(grade(10).begin(), grade(10).end(), 0)

However user may do not know or forget this rule.

And of course your function must always return reference to existing data. For example if no such array at specified index you need to create appropriate array at specified index and use it for return.

And of course function must not return reference to any non-static variable declared in function body because all of them will be destroyed after function return and before return value will be used. Which guarantee crash of aplication. Use return by value for that case.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM