【问题标题】:(Re)named std::pair members(重新)命名 std::pair 成员
【发布时间】:2011-08-30 08:34:22
【问题描述】:

我不想写town->first,而是写town->name。内联命名访问器(Renaming first and second of a map iteratorNamed std::pair members)是迄今为止我找到的最佳解决方案。我对命名访问器的问题是类型安全的损失: pair<int,double> 可以参考struct { int index; double value; }struct { int population; double avg_temp; }。谁能提出一个简单的方法,也许类似于特征?

我经常想从一个函数中返回一个对或一个元组,每次引入一个像struct city { string name; int zipcode; } 和它的ctor 这样的新类型是很累人的。我很高兴了解 boost 和 C++0x,但我需要一个没有 boost 的纯 C++03 解决方案。

更新

关于 andrewdski 的问题:是的,像 pair<int=index, double=value> 这样的(假设的)语法会创建一个与 pair<int=population, double=avg_temp> 不同的类型,这将满足您的要求。我什至不介意必须实现自定义对/元组模板类 ONCE 并在需要新类型时适当地将“名称特征”模板参数传递给它。我不知道“名字特征”会是什么样子。也许这是不可能的。

【问题讨论】:

  • 这不是一个真正的问题吗?为什么投反对票?
  • @Bo Persson:在投票之前你应该先阅读这个问题:我在哪里提到了继承?
  • @Bo Persson 对于较大的对象有 std::tuple / boost::tuple。
  • 也许问题在于人们不太了解您的要求。你说你想区分struct { int index; double value; }struct { int population; double avg_temp; },但是你想通过重命名pair成员来做到这一点?这到底是怎么回事?看起来你肯定想要一个结构。 (为了记录,我没有投反对票。)
  • 那么像pair<int=index, double=value> 这样的(假设的)语法会创建一个与pair<int=population, double=avg_temp> 不同的类型,是否满足您的要求?

标签: c++ templates stl typedef traits


【解决方案1】:

我看不出你怎么能做得更好

struct city { string name; int zipcode; };

那里没有什么是非必要的。你需要这两个成员的类型,你的整个问题都是围绕给这两个成员命名,并且你希望它是一个唯一的类型。

您知道聚合初始化语法,对吧?你不需要构造函数或析构函数,编译器提供的就可以了。

示例:http://ideone.com/IPCuw


类型安全要求您引入新类型,否则pair<string, int> 在 (name, zipcode) 和 (population, temp) 之间是不明确的。

在 C++03 中,返回一个新元组需要:

city retval = { "name", zipcode };
return retval;

或者写一个方便的构造函数:

city::city( std::string newName, int newZip ) : name(newName), zipcode(newZip) {}

得到

return city("name", zipcode);

但是,使用 C++0x,您将可以编写

return { "name", zipcode };

并且不需要用户定义的构造函数。

【讨论】:

  • 你打败了我。但是我应该补充一点,使用 string 聚合初始化语法将不起作用。
  • 可能会很好地扩展一般谷歌受众的聚合初始化语法
  • @Ben:我不知道它可以在内部使用 std::string。 Gcc 用-ansi 接受它。请不要考虑我的第一条评论。
  • @Alexandre:如果不允许这样做会很成问题:数组初始化使用相同的聚合初始化器
  • @Ben Voigt:赞成并感谢。不幸的是,扩展初始化列表在 C++03 中不可用,您不能在函数末尾写 return { "new york", 10001 };。所以你还是要写一个ctor,写return city("new york", 10001);。我需要一个纯 C++03 解决方案。
【解决方案2】:

我想详细说明

struct City : public std::pair<string, int> {
  string& name() { return first; }
  const string& name() const { return first; }
  int& zip() { return second; }
  int zip() const { return second; }
};

是最接近你所寻找的东西,尽管struct City { string name; int zipcode; } 看起来非常好。

【讨论】:

  • 引用问题:内联命名访问器(12)是迄今为止我找到的最佳解决方案
【解决方案3】:

虽然不完美,但可以使用标记数据:

template <typename tag_type, typename pair_type>
typename tag_type::type& get(pair_type& p);

typedef std::pair<std::string /*name*/, int /*zipcode*/> city;
struct name { typedef std::string type; };
struct zipcode { typedef int type; };

template <>
std::string& get<name, city>(city& city)
{
   return city.first;
}

template <>
int& get<zipcode, city>(city& city)
{
   return city.second;
}

int main()
{
   city c("new york", 10001);
   std::string n = get<name>(c);
   int z = get<zipcode>(c);
}

但正如 Ben Voigt 所说:struct city { string name; int zipcode; }; 几乎总是会更好。

编辑:模板可能是一种过度杀伤,您可以在命名空间中使用免费函数。这仍然不能解决类型安全问题,因为任何 std::pair&lt;T1, T2&gt; 都与任何其他 std::pair&lt;T1, T2&gt; 类型相同:

namespace city
{
   typedef std::pair<std::string /*name*/, int /*zipcode*/> type;

   std::string& name(type& city)
   {
      return city.first;
   }

   int& zipcode(type& city)
   {
      return city.second;
   }
}

int main()
{
   city::type c("new york", 10001);
   std::string n = city::name(c);
   int z = city::zipcode(c);
}

【讨论】:

  • 我必须对此表示赞同...这正是我的建议 (boost::bimaps) 所做的
  • 感谢您为回答我的问题所做的努力。但是我还是不明白。为什么在结构中有那些 typedef?它们似乎没有被使用。而且类型安全问题没有解决,你不能有另一个typedef std::pair&lt;std::string /*street*/, int /*number*/&gt; building;。那是我对命名访问器的问题。
  • @Ali:所有std::pair&lt;T1, T2&gt; 与任何其他std::pair&lt;T1, T2&gt; 类型相同。如果你想要类型安全,你需要使用不同的类型。
  • 是的,我知道。我什至不介意必须实现自定义对/元组模板类 ONCE 并在需要新类型时适当地将“名称特征”模板参数传递给它。我不知道“名字特征”会是什么样子。也许这是不可能的。你看到我想要什么了吗?
  • 赞成您的回答并感谢。也许一个普通的struct city { string name; int zipcode; }; 确实是最简单的解决方案,使用 C++0x 我将能够使用聚合初始化。
【解决方案4】:

由于std::pair 通常用于在std::map 容器中存储条目,您可能需要查看tagged elements in Boost Bimap

简介:

#include <boost/bimap/bimap.hpp>
#include <string>
#include <iostream>

struct name {}; // Tag for the default 'first' member
struct zipcode {}; // Tag for the default 'second' member

int main()
{
    using namespace boost::bimaps;
    typedef bimap <tagged<std::string, name>, tagged<int, zipcode> > Cities;
    typedef Cities::value_type registration;

    Cities cities;
    cities.insert(registration("Amsterdam", 20));
    cities.insert(registration("Rotterdam", 10));

    // ...
    std::string cityName;
    std::cin >> cityName;

    Cities::map_by<name>::const_iterator id_iter = cities.by<name>().find(cityName);
    if( id_iter != cities.by<name>().end() )
    {
        std::cout << "name: " << id_iter->get<name>() << std::endl
                  << "zip: " << id_iter->get<zipcode>()   << std::endl;
    }

    return 0;
}

请注意,bimap 可以透明地模拟std::map 或其他关联容器类型,而不会降低性能;他们只是更灵活。在这个特定的示例中,定义很可能最好更改为:

typedef bimap <tagged<std::string, name>, multiset_of<tagged<int, zipcode> > > Cities;
typedef Cities::value_type registration;

Cities cities;
cities.insert(registration("Amsterdam", 20));
cities.insert(registration("Rotterdam", 10));
cities.insert(registration("Rotterdam", 11));

我邀请您浏览 Boost Bimap 的文档以了解全貌

【讨论】:

  • 感谢您的回答。我很高兴了解有关 boost 的更多信息。请看我的评论sehe的回答,我在理解上有些问题。
【解决方案5】:

您可以使用指向成员的运算符。有几种选择。这是最直接的。

typedef std::map< zipcode_t, std::string > zipmap_t;
static zipcode_t const (zipmap_t::value_type::*const zipcode)
                                              = &zipmap_t::value_type::first;
static std::string (zipmap_t::value_type::*const zipname)
                                              = &zipmap_t::value_type::second;

// Usage
zipmap_t::value_type my_map_value;
std::string &name = my_map_value.*zipname;

您可以将一种伪类型的访问器放入专用的namespace 中,以将它们与其他事物分开。然后它看起来像my_map_value.*zip::name。但是,除非你真的需要使用pair,否则只定义一个新的struct 可能更容易。

【讨论】:

  • 是的,我相信如果定义一个新结构,代码是最容易理解的。如果我必须使用对,我将使用内联访问器。不管怎样,谢谢你的回答,点赞!
【解决方案6】:

我想出了一个可以像这样使用的Utility_pair 宏:

Utility_pair(ValidityDateRange,
    time_t, startDay,
    time_t, endDay
);

那么,当你需要访问ValidityDateRange的字段时,你可以这样做:

ValidityDateRange r = getTheRangeFromSomewhere();

auto start = r.startDay(); // get the start day
r.endDay() = aNewDay();    // set the end day
r.startDay(aNewDay1())     // set the start and end day in one go.
 .endDay(aNewDay2());

这是实现:

#include <utility>

#define Utility_pair_member_(pairName, ordinality, type, name)      \
    const type &name() const { return ordinality; }                 \
    type &name() { return ordinality; }                             \
    pairName &name(const type &m) { ordinality = m; return *this; } \
/***/

#define Utility_pair(pairName, firstMemberType, firstMemberName, secondMemberType, secondMemberName) \
    struct pairName: std::pair<firstMemberType, secondMemberType>  {                                 \
        Utility_pair_member_(pairName, first, firstMemberType, firstMemberName)                      \
        Utility_pair_member_(pairName, second, secondMemberType, secondMemberName)                   \
    }                                                                                                \
/***/

【讨论】:

    【解决方案7】:

    可能不值得额外的内存,但如果您想保留std::pairstd::tuple/ 的优势并保持与不可更改的 API 的兼容性,您可以从它们继承,然后定义对其成员的引用。

    除了内存成本之外,还有一个巨大的警告,即 STL 类型通常没有虚拟析构函数,因此如果您尝试使用指向基类型的指针取消分配,可能会导致内存泄漏:

    #include <tuple>
    
    struct PairShadow : public std::pair<int, double> {
        using std::pair<int, double>::pair;
        PairShadow & operator=(PairShadow const &) { /*call parent op here*/ }
        int & x = this->first;
        double & y = this->second;
    };
    
    struct TupleShadow: public std::tuple<int, double> {
        using std::tuple<int, double>::tuple;
        PairShadow & operator=(PairShadow const &) { /*call parent op here*/ }
        int & x = std::get<0>(*this);
        double & y = std::get<1>(*this);
    };
    
    auto main() -> int {
        auto twinShadow = PairShadow(1,3.0);
        auto tupleShadow = TupleShadow(1,3.0);
        return tupleShadow.y;
    }
    

    或者,也许最好像@holyblackcat 提到的那样,在许多情况下,只要您不尝试遵循特别需要@987654325 的不可更改的API,您也可以只为您的结构定义std::get&lt;int&gt;(PairShadow) 的重载@或std::tuple

    【讨论】:

    • 这会带来内存(可能还有性能)开销,并使元组不可分配。恕我直言,更好的选择是使用常规的struct,就像接受的答案所暗示的那样,并可能专门为它使用std::get(和相关功能)。
    • 我非常同意使用常规结构可能会更好,并且这会产生不必要的内存成本,但是可以通过定义赋值运算符来解决赋值问题,尽管显然会导致令人讨厌的简洁性。我将添加更多警告。
    【解决方案8】:

    我认为你真的应该在这里引入新类型。在有一个pair类方面我完全站在stl的一边,但这就是为什么java人认为他们不想有一个pair类的确切原因,你应该总是为你的类似piar的类型引入新的类型。

    stl 解决方案的好处是您可以使用通用类对,但只要您真的希望成员以不同于 first/second 的方式命名,您就可以引入新的类型/类。最重要的是,引入新类让您可以在必要时轻松添加第三个成员。

    【讨论】:

      【解决方案9】:

      也许您可以从 pair 继承您自己的 pair 类,并在构造函数中设置两个引用,称为 name 和 zipcode(只要确保实现您将使用的所有构造函数)

      【讨论】:

      • std::pair 没有虚拟析构函数,因此这可能会导致意外的内存泄漏。
      • 谢谢。但是如果你引入一个新类型,为什么不引入 struct city { string name;国际邮政编码; } 首先?
      • @Travis:是的,您应该声明自己的析构函数(它将调用父级的析构函数)以及构造函数 @Ali:通过从 pair 继承,您可以使用它的所有功能(例如,当预定义函数需要 pair 作为它的参数,如果你使用从 pair 继承的 ownPair ,编译器会自动转换你的类型来配对)
      • 这不是问题。如果 anything 被添加到需要销毁的类中,则这些析构函数将不会被任何只需要 std::pair codersource.net/c/c-miscellaneous/c-virtual-destructors.aspx 的东西调用
      猜你喜欢
      • 1970-01-01
      • 2013-04-14
      • 1970-01-01
      • 1970-01-01
      • 2016-07-14
      • 1970-01-01
      • 1970-01-01
      • 2011-10-04
      相关资源
      最近更新 更多