1

I want to create an object only if some conditions are applied, otherwise retun nullptr. This is how I would do it in Delphi (2009+):

function GetGen(n : integer) : Generics.Collections.TList<Integer>;
var
i : integer;
begin
  result := nil;
  if n > 0 then begin
      result := Generics.Collections.TList<Integer>.Create;
      for i := 0 to n - 1 do result.Add(i);
  end;
end;

procedure TestGenList(n : integer);
var
   aInt : integer;
   aGen : Generics.Collections.TList<Integer>;
begin
   aGen := GetGen(n);
   if aGen = nil then begin
      WriteLn('No generic created!');
      Exit;
   end;
   WriteLn(Format('Size: %d', [aGen.Count]));
   for aInt in aGen do Write(Format('%d ', [aInt]));
   aGen.Free; //will clear integers
end;

procedure TestGen
begin
    TestGenList(0);
    Readln;
    TestGenList(5);
    Readln;
end.

This is how I could do it in C++ :

unique_ptr<vector<int>> GetUniquePrtVec(int n){
    if (n < 1) return(nullptr); //create only if correct input is given
    unique_ptr<vector<int>> result (new vector<int>);
    for (int i = 0 ; i != n; i++){
        result->push_back(i);
    }
    return(move(result));
}

void TestPtrVec(int n){
    unique_ptr<vector<int>> vec = GetUniquePrtVec(n);
    if (vec == nullptr){
        cout << "No vector created" << endl;
        return;
    }
    cout << endl << vec->size() << endl;
    for_each(vec->begin(), vec->end(), [](int n){cout << n << " " << endl;});
    vec->clear(); //clear vector
    vec.reset(nullptr);
}

void testVec3(){
    TestPtrVec(0);
    TestPtrVec(5);
}

My question is about the right idiom. Would you guys, experienced C++ programmers (for I am a beginner, just learning the language), do it this way? If not, then how would you do it?

Thanks.

Mihaela
  • 2,482
  • 3
  • 21
  • 27
  • You don't seem to be a beginner from the C++ code you write ! – iammilind Mar 09 '12 at 14:48
  • Thanks, I'm beginner in C++, not programming :) To elaborate my dilemma: would you check for a nullptr, or have a vec ptr variable somewhere (in another func) pointing to an empty vector? – Mihaela Mar 09 '12 at 14:49
  • 2
    This is hard to answer out of context. A function `GetUniquePtrVec` sounds pretty useless (you can just make the vector at the site where it's needed). Moreover, `vector` is already a dynamic container class, so it's very unusual to wrap it in an extra layer of dynamic management. Personally, I would make a guess that unsigned integers are a better approach here. – Kerrek SB Mar 09 '12 at 14:54
  • 1
    It is not necessary to call `move` on a return value when it is a local variable. – Benjamin Lindley Mar 09 '12 at 15:07
  • @KerrekSB: yes unsigned int is surely a better approach, sorry :). So you kinda answered my question. I should not use this idiom. – Mihaela Mar 09 '12 at 15:24

5 Answers5

1

personally I believe that having a pointer to a vector is a bit necessary it looks as to me as if you could just return an empty vector or even throw an invalid argument error. The whole null return value is a bit of a hack and now you have to manage some memory because of it.

I personally would rather see

std::vector<int> get_vec(int n){
    std::vector<int> result;
    if(n < 1) return result;
    result.reserve(n);
    for (int i = 0 ; i != n; i++){
        result.push_back(i);
    }
    return result; 
}

or

std::vector<int> get_vec(int n){
    if(n < 1) throw std::invalid_argument("n must be greater than 1");
    std::vector<int> result;
    result.reserve(n);
    for (int i = 0 ; i != n; i++){
        result.push_back(i);
    }
    return result;
}


void test(int n){
    try{
    std::vector<int> vec = get_vec(n);
    catch(const std::exception& e)
    {
        std::cerr << "No vector created: " << e.what() << std::endl;
        return;
    }

//etc. .  .
111111
  • 15,686
  • 6
  • 47
  • 62
  • result.reserve(n) a good point, but I wanted to avoid copying. What if I have a vector with 100k elements? Isn't return(result) gonna invoke copy constructor? Or am I getting this wrong? – Mihaela Mar 09 '12 at 15:01
  • @Mihaela: Yes, as the compiler will first try to *move*, only if that's not possible it will try to copy. See my answer. – Xeo Mar 09 '12 at 15:03
  • @Mihaela as you are using move i am assuming you have c++11, in which case it will be moved no copied. this is a very light weight operation such swapping a pointer about. actually putting `return std::move(result);` is more likely to stop the compiler from making the cherished copy elision optimization. finally return is no a function putting brackets around it is odd. – 111111 Mar 09 '12 at 15:07
  • OK, just tell me if I got this, I should use move almost exclusively when writing my classes (like move constructors). And also assume that the compiler will try to move first always, then copy. How do I know that the move constructor exists, look through the debugger? – Mihaela Mar 09 '12 at 15:10
  • ermm, not quite, there are times when the compiler know when to move for example in a return (because everything is going to be delete) in these cases just like the var name as is, this is because there is a further optimization that can be made by the compiler and the more `code clutter` there is the harder it is for the compiler to introduce the optimization. – 111111 Mar 09 '12 at 15:12
  • So I shouldn't "act smart" :) and try to do optimization myself. I guess I have more reading to do :) Thanks. – Mihaela Mar 09 '12 at 15:18
  • hmm, not even that it is just the return case doesn't need a move. for example if you are 'done' with an object half we through a function and you want to push it back into a container then it would make perfect sense to `move` it because the compiler doesn't know that case. – 111111 Mar 09 '12 at 15:22
1

IMHO, the best way for your example, would be to simply return the std::vector by value and simply return an empty one if the input is invalid.

std::vector<int> get_vec(int n){
  std::vector<int> ret;
  for(unsigned i=0; i < n; ++i)
    ret.push_back(i);
  return ret; // will be empty for (n < 1)
              // and will be moved if (n >= 1)
}

One thing you need to learn: You don't need to explicitly std::move if you return a local variable. Just return by value. If copy elision is possible, it will do that (RVO / NRVO). If it can't for some reason, it'll first try to move it out before copying it. Note however, that a member of a local variable will not be moved automatically, aka

struct object{ std::vector<int> member; };

std::vector<int> foo(){
  object o;
  // ...
  return o.member; // no move, no copy elision, plain old copy
}

Now, your second function can also be improved and reduced:

void try_vec(int n){
  auto vec = get_vec(n); // will elide copy or simply move
  for(auto& x : vec) // will not loop if vector is empty
    std::cout << x << ' '; // why space and newline?
  std::cout << "\n"; // don't use std::endl, it also flushes the stream
}

And from your original function:

vec->clear(); //clear vector
vec.reset(nullptr);

Is not needed, that's the whole reason for smart pointers and resource managing containers. They will destroy what they own when they go out of scope.

Xeo
  • 129,499
  • 52
  • 291
  • 397
1

Seems what you need is something like boost::optional. Here is an example of its usage:

optional<char> get_async_input()
{
    if ( !queue.empty() )
        return optional<char>(queue.top());
    else return optional<char>(); // uninitialized
}

void receive_async_message()
{
    optional<char> rcv ;
    // The safe boolean conversion from 'rcv' is used here.
    while ( (rcv = get_async_input()) && !timeout() )
        output(*rcv);
}

For more information refer to boost documentation.

  • @Mihaela: Boost is the stdlib on steroids. And please don't call the standard library "STL", that part is the name of the container / algorithm / iterator / function objects subpart. – Xeo Mar 09 '12 at 15:03
  • Sorry, I meant Standard Library, (STL < SL) – Mihaela Mar 09 '12 at 15:07
  • Just a clarification about my not using Boost. I stared learning C++ less than a month ago, and STL, syntax, language and all that is going on "underneath" is too much to grasp. I want to "get the feel of the language", try to eliminate some of my "old idioms" from OOP Pascal, and do it the C++ way.So Boost would be too much for me to comprehend at the moment. See just how many mistakes I made in my question, design and code-wise. So I will be posting more questions here :) – Mihaela Mar 09 '12 at 20:24
-1

Use exceptions or type erasure, returning NULL is the C way of doing things, not the C++ way.

Also you use the move semantic but you are not returning an r-value, it would not work like that.

  • 1
    Your idea about move semantics is wrong. It would be *especially bad* if the return type was an rvalue reference. And returning by value is exactly the point where you use move semantics. Also, returning `nullptr` is perfectly fine in C++, *if* you must use pointers. Otherwise, `boost::optional` is also a great candidate. And what would type erasure be needed for here?! – Xeo Mar 09 '12 at 15:00
-1

Im a little unfamilliar with this syntax, but I think it looks okay to me. Though, why not just use pointers with the usual c+ syntax?

vector<int> GetUniquePrtVec(int n)
{
    if (n < 1) 
        return null;

    vector<int>* result = new vector<int>;
    for (int i = 0 ; i != n; i++){
        result->push_back(i);
    }
    return (result);
}

Though Ive never used a vector pointer. Generally when I create a vector I pass it to a function by reference, like this:

vector<int> myVec;
bool bSuccess = PopulateVec(n, myVec);

vector<int>* PopulateVec(int inNum, vector<int>& inVec)
{
    if (inNum< 1)
        return false;

    for (int i = 0 ; i != inNum; i++)
    {
        inVec->push_back(i);
    }

    // inVec is "returned" by reference
    return true
}
MintyAnt
  • 2,978
  • 7
  • 25
  • 37
  • I think you should look over your code again, as it got several problems. Also, ***no***, owning raw pointers are a *friggin bad idea*. Don't use them. Use smart pointers, *if you have to do dynamic allocation*. – Xeo Mar 09 '12 at 15:07