简体   繁体   中英

std::size_t vs. size_type as parameters and function return types

Suppose I have this code. Which method is better?

// Method 1
std::size_t size()
{
  // m_myVector is of type std::vector<MyClass*> in all examples
  return m_myVector.size();
}

// Method 2
std::vector<MyClass*>::size_type size()
{
  // m_myVector is of type std::vector<MyClass*> in all examples
  return m_myVector.size();
}

The first way works in 99% of cases, but of course there's the off-chance that the size of the vector isn't of type std::size_t . That said, we can just rely on a software analysis tool to tell us that the return type of the size of the vector has changed.

The second way exposes implementation detail of the vector to the caller. Does this break encapsulation? I don't know, you tell me!

Here's another more complex example. Which method is better?

void doFoo(const SomeClass& someObject)
{
  // These could be ints or size_types... Feel free to use your imagination
  std::size_t firstCount = someObject.getFirstCount();
  for (std::size_t i = 0U; i < firstCount; ++i)
  {
    foo(firstCount);
  }
}

void doFoo2(const SomeClass& someObject)
{
  // I thought I'd provide another example to help your imagination :)
  std::vector<MyClass*>::size_type secondCount = someObject.getSecondCount();
  for (std::vector<MyClass*>::size_type i = 0U; i < secondCount; ++i)
  {
    foo(secondCount);
  }
}

void doFoo3(const SomeClass& someObject)
{
  // The correct styling would be: NotMyClass*
  // But I really wanted to emphasize this was different from above, so I ALL CAPPED IT
  std::vector<NOTMYCLASS*>::size_type thirdCount = someObject.getThirdCount();
  for (std::vector<NOTMYCLASS*>::size_type i = 0U; i < thirdCount; ++i)
  {
    foo(thirdCount);
  }
}

// Method 1
void foo(std::size_t index)
{
  // m_myVector is of type std::vector<MyClass*> in all examples
  m_myVector.at(index)->doLotsOfStuff();
}

// Method 2
void foo(std::vector<MyClass*>::size_type index)
{
  // m_myVector is of type std::vector<MyClass*> in all examples
  m_myVector.at(index)->doLotsOfStuff();
}

Okay this is a lengthy example so I'll explain what's going on. doFoo() , doFoo2() , and doFoo3() all call foo() which takes in a std::size_t in the first implementation or std::vector<MyClass*>::size_type in the second implementation.

doFoo() is passing in a std::size_t to foo() , so the first implementation of foo() makes more sense here but then we index a std::vector<MyClass*> which is expecting a std::vector<MyClass*>::size_type . Not particularly great if size_type was not defined to be std::size_t .

doFoo2() is passing in a std::vector<MyClass*>::size_type to foo() , so the second implementation of foo() works very nicely here. No gripes other than exposing the implementation details of the private vector to the caller. And finally I'd imagine that we would need to include a separate header for MyClass .

doFoo3() is passing in a std::vector<NOTMYCLASS*>::size_type to foo() ... And neither implementation of foo() expects this because the only vector that foo() cares about is the vector which holds elements of type MyClass* . Now, as an academic question, is std::vector<NOTMYCLASS*>::size_type always the same as std::vector<MyClass*>::size_type ? I actually don't know the answer to this one, but I've been hearing 'yes' and 'no' . And finally, there's that issue of encapsulation once again (if it is an issue).

Anyways, thanks for bearing with me. Thoughts?

but of course there's the off-chance that the size of the vector isn't of type std::size_t

Such off-chance doesn't practically exist in this case because std::vector<MyClass*>::size_type is (indirectly guaranteed and required to be) of type std::size_t . Using std::size_t is fine in this case, and it doesn't leak unnecessary implementation details.


In the case of standard containers, Container::size_type is defined based directly on on what allocator is being used. Thus, using size_type is typically only necessary when the allocator type - or the container type itself - is templated. In the allocator case, you can use allocator traits instead of the container member type which allows you to keep the container type hidden. If the container type itself is templated, then there is no point in hiding it since only someone who knows the container could have instantiated the template in the first place.

Furthermore, you can hide - or rather obfuscate (in a positive, encapsulating way) - the function declaration by creating a type alias member, just like std::vector has a type alias member based on its allocator.

Example:

template<class Alloc>
class Foo
{
    // could be hidden with PIMPL if desired
    std::vector<MyClass*, Alloc> m_myVector;

public:
    // Since C++11
    using size_type = typename std::allocator_traits<Alloc>::size_type;
    // Prior to C++11
    typedef typename Alloc::size_type size_type;
    
    size_type size();
};

std::size_t is a type capable of holding the size of any array, including arrays allocated through allocators.

This means that std::size_t will always be able to store the result of std::vector<T>::size() , so method 1 will never lead to an overflow and is perfectly readable.

There is no guarantee that std::vector<T>::size_type is the same for all T , but you would be hard-pressed to find an implementation of std::vector where size_type is not always std::size_t .

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