选择c++中的构造函数

choose which constructor in c++

本文关键字:构造函数 c++ 选择      更新时间:2023-10-16

我正在通过构建我自己的vector版本来练习c++,名为" vector "。我有两个构造函数,填充构造函数和范围构造函数。它们的声明如下:

template <typename Type> 
class Vector {
public:
    // fill constructor
    Vector(size_t num, const Type& cont);
    // range constructor
    template<typename InputIterator> Vector(InputIterator first, InputIterator last);
    /* 
    other members
    ......
    */
}

fill构造函数用val的num填充容器;范围构造函数将范围[first, last]中的每个值复制到容器中。它们应该与STL vector的两个构造函数相同。

它们的定义如下:

//fill constructor 
template <typename Type> 
Vector<Type>::Vector(size_t num, const Type& cont){
    content = new Type[num];
    for (int i = 0; i < num; i ++)
        content[i] = cont;
    contentSize = contentCapacity = num;
}
// range constructor
template <typename Type> 
template<typename InputIterator>
Vector<Type>::Vector(InputIterator first, InputIterator last){
    this->content = new Type[last - first];
    int i = 0;
    for (InputIterator iitr = first; iitr != last; iitr ++, i ++)
    *(content + i) = *iitr;
    this->contentSize = this->contentCapacity = i;
}

然而,当我尝试使用它们时,我无法区分它们。例如:

Vector<int> v1(3, 5);
在这行代码中,我打算创建一个包含三个元素的Vector,每个元素都是5。但是编译器会选择范围构造函数,将"3"answers"5"都当作"InputIterator"的实例,这毫无疑问会导致错误。当然,如果我把代码改成:
Vector<int> v1(size_t(3), 5);

一切正常,填充构造函数被调用。但这显然不是直观和用户友好的。

那么,有没有一种方法可以直观地使用填充构造函数?

您可以使用std::enable_if(或boost::enable_if,如果您不使用c++ 11)来消除构造函数的歧义。

#include <iostream>
#include <type_traits>
#include <vector>
using namespace std;

template <typename Type> 
class Vector {
public:
    // fill constructor
    Vector(size_t num, const Type& cont)
    { 
        cout << "Fill constructor" << endl;
    }
    // range constructor
    template<typename InputIterator> Vector(InputIterator first, InputIterator last,
        typename std::enable_if<!std::is_integral<InputIterator>::value>::type* = 0)
    { 
        cout << "Range constructor" << endl;
    }
};
int main()
{
    Vector<int> v1(3, 5);
    std::vector<int> v2(3, 5);
    Vector<int> v3(v2.begin(), v2.end());
}


上面的程序应该首先通过检查类型是否为整型(因此不是迭代器)来调用fill构造函数。


顺便说一下,在范围构造函数的实现中,应该使用std::distance(first, last)而不是last - first。在迭代器上显式地使用-运算符将您限制为RandomAccessIterator类型,但您希望支持InputIterator,这是最泛型的迭代器类型。

甚至std::vector似乎也有这个问题。

std::vector<int> v2(2,3);
选择

template<class _Iter>
        vector(_Iter _First, _Iter _Last)

在Visual c++中,尽管它应该更接近于非模板化的情况..

编辑:上面的函数(正确地)将构造委托给下面的函数。我完全迷路了。

template<class _Iter>
        void _Construct(_Iter _Count, _Iter _Val, _Int_iterator_tag)

编辑2啊!:

下面的函数以某种方式标识要调用构造函数的哪个版本。

template<class _Iter> inline
    typename iterator_traits<_Iter>::iterator_category
        _Iter_cat(const _Iter&)
    {   // return category from iterator argument
    typename iterator_traits<_Iter>::iterator_category _Cat;
    return (_Cat);
    }

上面显示的_Construct函数在第三个变量上(至少)有2个版本的重载,该变量是上面_Iter_cat函数返回的标记。根据该类别的类型选择正确的_Construct过载。

最后的编辑:iterator_traits<_Iter>是一个似乎为许多不同的常见变种模板化的类,每个都返回适当的"Category"类型

解决方案:似乎第一个参数类型的模板特化是std库在MS vc++中处理这种混乱情况(原始值类型)的方式。也许你可以调查一下,然后效仿一下?

问题出现了(我认为),因为对于原始值类型,Typesize_t变量是相似的,因此选择了具有两个相同类型的模板版本。

这个问题与标准库实现所面临的问题相同。有几种方法可以解决它。

  • 你可以为所有整型提供非模板重载的构造函数(代替第一个形参)。

  • 您可以使用基于sfinae的技术(如enable_if)来确保不为整数参数选择范围构造函数。

  • 在检测到整型参数(通过使用is_integral)后,可以在运行时(通过使用if)分支range构造函数,并将控制重定向到正确的构造代码。分支条件将是一个编译时值,这意味着编译器可能会在编译时减少代码。

  • 您可以简单地查看您的标准库实现版本,看看它们是如何做到的(尽管从抽象c++语言的角度来看,它们的方法并不需要可移植和/或有效)。

这种模糊性给早期的库实现带来了问题。这就是"做正确的事"。的效果。据我所知,你需要SFINAE来解决它……这可能是该技术的第一个应用之一。(一些编译器欺骗和破坏了它们的重载解析内部,直到在核心语言中找到解决方案。)

这个问题的标准规范是c++ 98和c++ 03之间的关键区别之一。源自c++ 11,§23.2.3:

14对于本条款和第21条中定义的每个序列容器:

-如果构造函数

       template <class InputIterator>
       X(InputIterator first, InputIterator last,
         const allocator_type& alloc = allocator_type())

使用不符合输入迭代器条件的InputIterator类型调用,则构造函数不参与重载解析。

15实现在多大程度上决定了一个类型不能作为输入迭代器,除了作为最小整型不能作为输入迭代器。