Detecting negative number
I have a code in which user has to pass > 0 number otherwise this code will throw. Using type of thi开发者_Python百科s arg as std::size_t doesn't work for the reason that negative numbers give large positive numbers. Is it good practice if I use signed type or are there other ways to enforce it?
void f(std::size_t number)
{
//if -1 is passed I'm (for obvious reason) get large positive number
}
I don't think there's a definitive correct answer to this question. You can take an look to Scott Meyers's opinion on the subject :
One problem is that unsigned types tend to decrease your ability to detect common programming errors. Another is that they often increase the likelihood that clients of your classes will use the classes incorrectly.
In the end, the question to ask is really : do you need the extra possible values provided by the unsigned type ?
A lot depends on what type of argument you imagine your clients trying to pass. If they're passing int, and that's clearly big enough to hold the range of values you're going to use, then there's no practical advantage to using std::size_t - it won't enforce anything, and the way the issue manifests as an apparently huge number is simply more confusing.
BUT - it is good to use size_t anyway as it helps document the expectations of the API.
You clearly can't do a compile-time check for "> 0" against a run-time generated value, but can at least disambiguate negative inputs from intentional huge numbers ala
template <typename T>
void f(T t)
{
if (!(t > 0))
throw std::runtime_error("be positive!");
// do stuff with t, knowing it's not -1 shoehorned into size_t...
...
}
But, if you are really concerned about this, you could provide overloads:
// call me please e.g. f(size_t(10));
void f(size_t);
// unimplemented (and private if possible)...
// "want to make sure you realise this is unsigned: call f(size_t) explicitly
void f(int32_t);
void f(int64_t);
...then there's a compile-time error leading to the comments re caller explicitly providing a size_t argument (casting if necessary). Forcing the client to provide an arg of size_t type is a pretty good way to make sure they're conscious of the issue.
Rin's got a good idea too - would work really well where it works at all (depends on there being an signed int type larger than size_t). Go check it out....
EDIT - demonstration of template idea above...
#include <iostream>
template <typename T>
void f(T t)
{
if (!(t > 0))
std::cout << "bad call f(" << (int)t << ")\n";
else
std::cout << "good f(" << (int)t << ")\n";
}
int main()
{
f((char)-1);
f((unsigned char)255);
}
I had the same problems you're having: Malfunctioning type-casting of string to unsigned int
Since, in my case, I'm getting the input from the user, my approach was to read the data as a string and check its contents.
template <class T>
T getNumberInput(std::string prompt, T min, T max) {
std::string input;
T value;
while (true) {
try {
std::cout << prompt;
std::cin.clear();
std::getline(std::cin, input);
std::stringstream sstream(input);
if (input.empty()) {
throw EmptyInput<std::string>(input);
} else if (input[0] == '-' && std::numeric_limits<T>::min() == 0) {
throw InvalidInput<std::string>(input);
} else if ((sstream >> value) && (value >= min) && (value <= max)) {
std::cout << std::endl;
return value;
} else {
throw InvalidInput<std::string>(input);
}
} catch (EmptyInput<std::string> & emptyInput) {
std::cout << "O campo não pode ser vazio!\n" << std::endl;
} catch (InvalidInput<std::string> & invalidInput){
std::cout << "Tipo de dados invãlido!\n" << std::endl;
}
}
}
If your allowed value range for number
allows it use the signed std::ptrdiff_t
(like Alexey said).
Or use a library like SafeInt and have f
declared something like this: void f( SafeInt< std::size_t > i );
which throws if you'll call it with something like f( -1 );
.
Fisrt solution
void f(std::ptrdiff_t number) {
if (number < 0) throw;
}
Second solution
void f(std::size_t number) {
if (number > std::numeric_limits<std::size_t>::max()/2) throw;
}
Maybe you should wrap read-function to another function which purpose will be get int
and validate it.
EDIT: Ok int
was just first idea, so read and parse string
This is one of the situations where you cannot really do much. The compiler usually gives out a warning when converting signed to unsigned data-types, so you will have to trust the caller to heed that warning.
You could test this using bitwise operation, such as the following:
void f(std::size_t number)
{
if(number & (0x1L << (sizeof(std::size_t) * 8 - 1)) != 0)
{
// high bit is set. either someone passed in a negative value,
// or a very large size that we'll assume is invalid.
// error case goes here
}
else
{
// valid value
}
}
This code assumes 8-bit bytes. =)
Yes, large values will fail, but you could document that they are not allowed, if you really need to protect against this.
Who is using this API? Rather than using a solution like this, I would recommend that they fix their code. =) I would think that the "normal" best practice would be for callers to use size_t, and have their compilers complain loudly if they try to put signed values into them.
I had to think about this question a little, and this is what I would do.
If your function has the responsability to throw an exception if you pass a negative number, then your function's signature should accept a signed integer number. That's because if you accept an unsigned number, you won't ever be able to tell unambiguosly if a number is negative and you won't be able to throw an exception. IOW, you want complain to your assignment of throwing exception.
You should establish what is an acceptable input range and use a signed integer large enough to fully contain that range.
精彩评论