E
E. Robert Tisdale
Richard said:Would you give the same advice
if the object being modified were a million-element vector?
Yes.
Elaborate a little. An example might help.
You might be surprised
Richard said:Would you give the same advice
if the object being modified were a million-element vector?
Siemel said:If one is allowed to pass in a NULL integer (not zero, which is something),
meaning that 'i' is not supplied, then you have to choose the second
version. Otherwise prefer the first method as it makes it clear that the
caller has to pass in a valid integer.
Berk Birand said:Thanks to both for your answers. Also another question that pops in my
mind is whether to use references or pointers in function headers. Is it
better practice to declare a function as, say:
void afunc(int& i);
or is it better to declare it as
void afunc(int* i);
I know that in the second case, one has to explicitly dereference i
whenever one wants to access it. But this can be considered a good thing,
as it makes it more obvious that you are manipulating pointers, and the
values are going to change. On the other hand, it's just more work!
So, what do you say about this?
Cy said:int j = 0, k;
BigObject obj;
func1(j); // inputting an int
k = func2(j); // inputting an int and outputting another int
func3(&obj); // outputting an object
func4(obj); // inputting an object by const reference
func5(&j, &k); // outputting two ints
Of course many of these reasonable guesses could be wrong depending
on the actual declarations, but I think the code is easier to
understand if you write your declarations in such a way that they are
right! So that means:
1) express output by return value if possible
2) use a pointer to express output otherwise
3) express input by call-by-value or const reference
4) never use a non-const reference (looks like input)
E. Robert Tisdale said:Siemel Naran wrote:
A better option would be to overload the function
void afunc(void);
"to pass in a NULL integer".
Jonathan Turkanis said:4) seems questionable. There's a broad class of cases where you have an
object
that's noncopyable or expensive to copy -- such as a stream or a
container --
which you want to pass to a modifying algorithm.
Generally, if you believe that functions which operate on a objects of a
given
type should be non-members as often as possible,
then most of the functions
which would otherwise be non-const member functions will become non-member
functions taking a reference to non-const.
Jonathan
Cy said:This rule set suggests using a pointer in this case. I mentioned that
updating and output by pointers would be indistinguishable.
(I certainly do agree.)
There is no reason to make functions into member functions to avoid
non-const references.
Use pointers:
func3(&obj); // outputting an object
Again, outputting and updating are not distinguishable.
E. Robert Tisdale said:Yes.
Elaborate a little. An example might help.
You might be surprised
E. Robert Tisdale said:Yes.
Elaborate a little. An example might help.
You might be surprised
I'm thinking more likeRichard said:E. Robert Tisdale writes
Yes.
Elaborate a little. An example might help.
You might be surprised
struct I {
int i_;
/* and maybe some other stuff */
}
typedef std::vector<I> Vector;
/* Function which sets element p of a Vector to value q
As recommended, function doesn't modify its arguments
but returns a copy.
*/
Vector afunct (const Vector & v, int p, int q) {
Vector temp(v);
temp[p].i_ = q;
return temp;
}
int main(int argc, char* argv[]) {
Vector v(1000000);
for (int i = 0; i < 1000000; ++i) {
v = afunct(v, i, i);
}
/*...*/ return 0;
}
Still think it's a good idea?
#include <cmath>cat main.cc
Siemel said:Say you call a function find(x,y) that returns a pointer to an integer, and
NULL if the integer does not exist. Then one could just say
afunc(find(x, y));
where the signature of afunc is void afunc(int *).
Surely this is more convenient than
int * f = find(x, y);
if (f) afunc(*f);
else afunc();
E. Robert Tisdale said:I'm thinking more likeRichard said:E. Robert Tisdale writes
Richard Herring wrote:
Would you give the same advice
if the object being modified were a million-element vector?
Yes.
Elaborate a little. An example might help.
You might be surprised
struct I {
int i_;
/* and maybe some other stuff */
}
typedef std::vector<I> Vector;
/* Function which sets element p of a Vector to value q
As recommended, function doesn't modify its arguments
but returns a copy.
*/
Vector afunct (const Vector & v, int p, int q) {
Vector temp(v);
temp[p].i_ = q;
return temp;
}
int main(int argc, char* argv[]) {
Vector v(1000000);
for (int i = 0; i < 1000000; ++i) {
v = afunct(v, i, i);
}
/*...*/ return 0;
}
Still think it's a good idea?
#include <cmath>cat main.cc
#include <vector>
#include <iostream>
std::vector<double> ramp(size_t n) {
std::vector<double> x(n);
for (size_t j = 0; j < x.size(); ++j)
x[j] = j;
return x;
}
std::vector<double> sqrt(const std::vector<double>& x) {
std::vector<double> y(x.size());
for (size_t j = 0; j < x.size(); ++j)
y[j] = sqrt(x[j]);
return y;
}
std:stream& operator<<(std:stream& os,
const std::vector<double>& x) {
for (size_t j = 0; j < x.size(); ++j)
os << ' ' << x[j];
return os << std::endl;
}
int
main(int argc, char* argv[]) {
const
size_t n = 4;
const
std::vector<double> x = ramp(n);
std::cout << "x =" << std::endl;
std::cout << x << std::endl;
const
std::vector<double> y = sqrt(x);
std::cout << "x =" << std::endl;
std::cout << y << std::endl;
return 0;
}
The problem with your example is that
you have not justified modifying std::vector<I> v *in-place*.
You contrived a ridiculous *straw-man* function afunct
then *assign* the return value to v a million times!
Didn't you notice that assignment modifies v?
Some objects (I/O streams, for example) are not useful
unless they can be modified.
So my advice is,
"Avoid defining functions that modify their arguments
because they compel programmers to declare an pass variables
which may otherwise be constants in the rest of the program.
Reserve functions that modify their arguments
to situations where an object *must* be modified in-place."
For example, I think that almost everybody would agree that
void sqrt(std::vector<double>& x) {
std::vector<double> y(x.size());
for (size_t j = 0; j < x.size(); ++j)
x[j] = sqrt(x[j]);
}
would probably be a bad design.
Shezan said:E. Robert Tisdale said:Richard said:E. Robert Tisdale writes
Richard Herring wrote:
Would you give the same advice
if the object being modified were a million-element vector?
Yes.
Elaborate a little. An example might help.
You might be surprised
struct I {
int i_;
/* and maybe some other stuff */
}
typedef std::vector<I> Vector;
/* Function which sets element p of a Vector to value q
As recommended, function doesn't modify its arguments
but returns a copy.
*/
Vector afunct (const Vector & v, int p, int q) {
Vector temp(v);
temp[p].i_ = q;
return temp;
}
int main(int argc, char* argv[]) {
Vector v(1000000);
for (int i = 0; i < 1000000; ++i) {
v = afunct(v, i, i);
}
/*...*/
return 0;
}
Still think it's a good idea?
I'm thinking more like
#include <cmath>cat main.cc
#include <vector>
#include <iostream>
std::vector<double> ramp(size_t n) {
std::vector<double> x(n);
for (size_t j = 0; j < x.size(); ++j)
x[j] = j;
return x;
}
std::vector<double> sqrt(const std::vector<double>& x) {
std::vector<double> y(x.size());
for (size_t j = 0; j < x.size(); ++j)
y[j] = sqrt(x[j]);
return y;
}
std:stream& operator<<(std:stream& os,
const std::vector<double>& x) {
for (size_t j = 0; j < x.size(); ++j)
os << ' ' << x[j];
return os << std::endl;
}
int
main(int argc, char* argv[]) {
const
size_t n = 4;
const
std::vector<double> x = ramp(n);
std::cout << "x =" << std::endl;
std::cout << x << std::endl;
const
std::vector<double> y = sqrt(x);
std::cout << "x =" << std::endl;
std::cout << y << std::endl;
return 0;
}
The problem with your example is that
you have not justified modifying std::vector<I> v *in-place*.
You contrived a ridiculous *straw-man* function afunct
then *assign* the return value to v a million times!
Didn't you notice that assignment modifies v?
This isn't the point. The point is that
it's best not to return a large object by value, that's all.
If you really need to "justify modifying std::vector<I> v *in-place*"
then you should set 'n' to 10000000 in your code above,
you will probably notice a difference in performance.
This is good advice in most cases, but it cannot be used for all cases.
Obviously, if you have *very* large objects,
you must take a different approach.
For example, I think that almost everybody would agree that
void sqrt(std::vector<double>& x) {
std::vector<double> y(x.size());
for (size_t j = 0; j < x.size(); ++j)
x[j] = sqrt(x[j]);
}
would probably be a bad design.
Yes, I agree. For many reasons,
none of which have anything to do with this thread.
Why do we need 'y'?
And why do we need a sqrt function
that takes 'std::vector<double>&'?
What's wrong with plain old 'double sqrt(double)' and using 'for_each'
to apply it to each element in a vector (or *any* container).
E. Robert Tisdale said:I don't think you said what you meant to say here.
Of course it will take about 2.5 million times as long
to process 10 million elements as it does to process 4 elements.
I suspect that you meant that
void sqrt(std::vector<double>& x) {
std::vector<double> y(x.size());
for (size_t j = 0; j < x.size(); ++j)
x[j] = sqrt(x[j]);
}
would be faster than
std::vector<double> sqrt(const std::vector<double>& x) {
std::vector<double> y(x.size());
for (size_t j = 0; j < x.size(); ++j)
y[j] = sqrt(x[j]);
return y;
}
If that's what you meant, you're wrong.
But don't take my word for it.
Try it yourself.
No. you are wrong. Try it.
For example, I think that almost everybody would agree that
void sqrt(std::vector<double>& x) {
std::vector<double> y(x.size());
for (size_t j = 0; j < x.size(); ++j)
x[j] = sqrt(x[j]);
}
would probably be a bad design.
Yes, I agree. For many reasons,
none of which have anything to do with this thread.
Why do we need 'y'?
And why do we need a sqrt function
that takes 'std::vector<double>&'?
What's wrong with plain old 'double sqrt(double)' and using 'for_each'
to apply it to each element in a vector (or *any* container).
If it isn't obvious to you,
provide an example showing what you mean to do.
Note that your solution would compel the programmer
to declare a variable vector (y) to receive the result
even if y is never modified anywhere else in the program.
Shezan said:Why would you need this 'y' vector?
I suspect this is why you don't notice the difference in performance.
for (size_t j = 0; j < x.size(); ++j)
x[j] = sqrt(x[j]);
}
would be faster than
std::vector<double> sqrt(const std::vector<double>& x) {
std::vector<double> y(x.size());
for (size_t j = 0; j < x.size(); ++j)
y[j] = sqrt(x[j]);
return y;
}
If that's what you meant, you're wrong.
But don't take my word for it.
Try it yourself.
Just for the sake of argument, I tried it.
There is a big difference
when you are running with debugging information (no optimisation).
When you switch on optimisation,
the difference is reduced - but still not the same.
If you look at the code (and my comment above),
it's obvious why its not the same.
Personally, I would prefer good performance even in debug builds.
It just makes maintenance easier
I tried it. I still prefer taking a different approach
'for_each'For example, I think that almost everybody would agree that
void sqrt(std::vector<double>& x) {
std::vector<double> y(x.size());
for (size_t j = 0; j < x.size(); ++j)
x[j] = sqrt(x[j]);
}
would probably be a bad design.
Yes, I agree. For many reasons,
none of which have anything to do with this thread.
Why do we need 'y'?
And why do we need a sqrt function
that takes 'std::vector<double>&'?
What's wrong with plain old 'double sqrt(double)' and using
to apply it to each element in a vector (or *any* container).
If it isn't obvious to you,
provide an example showing what you mean to do.
Note that your solution would compel the programmer
to declare a variable vector (y) to receive the result
even if y is never modified anywhere else in the program.
I don't see anything wrong with this.
I trust you are an experienced enough programmer
to know not to make 'y' visible anywhere else.
Try to focus on higher level issues
(like scoping your data) and you'll find that
there is no real need to make 'y' const.
And your code will be cleaner too
E. Robert Tisdale said:Please show us an example of what you mean.
I'm pretty sure that you're confused.
Shezan said:{
{ // <-- new scope
// declare y
// and use it (localize)
struct I {
int i_;
/* and maybe some other stuff */
}
typedef std::vector<I> Vector;
/* Function which sets element p of a Vector to value q
As recommended, function doesn't modify its arguments
but returns a copy.
*/[snip]Vector afunct (const Vector & v, int p, int q) {
Vector temp(v);
temp[p].i_ = q;
return temp;
}
int main(int argc, char* argv[]) {
Vector v(1000000);
for (int i = 0; i < 1000000; ++i) {
v = afunct(v, i, i);
}
/*...*/ return 0;
}
Still think it's a good idea?
The problem with your example is that
you have not justified modifying std::vector<I> v *in-place*.
You contrived a ridiculous *straw-man* function afunct
then *assign* the return value to v a million times!
Didn't you notice that assignment modifies v?
E. Robert Tisdale said:Shezan said:{
{ // <-- new scope
// declare y
// const
std::vector<double> x(n);
// x is defined as a variable
// only so that I can initialize it with
extern void sqrt(std::vector<double>&);
sqrt(x);
// Now, x is supposed to be constant.
// and use it (localize)
g(x);
// Does x still have the same value?
// Or did function g modify it?
x[0] = 13.0; // error! x is supposed to be constant
// The compiler won't catch this error.
}
// "anywhere else in the program"
}
Shezan said:E. Robert Tisdale said:Shezan said:E. Robert Tisdale wrote:
Please show us an example of what you mean.
{
{ // <-- new scope
// declare y
// const
std::vector<double> x(n);
// x is defined as a variable
// only so that I can initialize it with
extern void sqrt(std::vector<double>&);
sqrt(x);
// Now, x is supposed to be constant.
// and use it (localize)
g(x);
// Does x still have the same value?
// Or did function g modify it?
x[0] = 13.0; // error! x is supposed to be constant
// The compiler won't catch this error.
}
// "anywhere else in the program"
}
If you really had code like this in your software,
I would suggest refactoring.
Want to reply to this thread or ask your own question?
You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.