Resolved Const v typedef enum v #Define

Discussion in 'iOS Programming' started by charmofmaking, Jun 5, 2013.

  1. charmofmaking, Jun 5, 2013
    Last edited: Jun 6, 2013

    charmofmaking macrumors member

    Joined:
    Feb 2, 2013
    #1
    Any recommendations on the best way to have simple flags that control the flow of code?

    By best I am looking for efficient yet readable code ;)

    Code:
    //this works but seems very long winded
    const int isStory=0;
    const int isMenu=1;
    const int isChallenge=2;
    
    int gameMode=isMenu;
    
    if(gameMode==isMenu){...}else{...}
    
    // or should I be using
    
    typedef enum {
        isStory,
        isMenu,
        isChallenge
    } gameMode;
    
    // or
    
    #Define isMenu 1;
    #Define isChallenge 2;
     
  2. ArtOfWarfare, Jun 5, 2013
    Last edited: Jun 5, 2013

    ArtOfWarfare macrumors 604

    ArtOfWarfare

    Joined:
    Nov 26, 2007
    #2
    Go with enum. This is exactly what it's designed for.

    const is long winded.

    #define is evil. Suppose you wrote a property for an object called isMenu, and that this object has somehow imported that #define statement:

    Code:
    // What you might write:
    
    @property bool isMatching;
    
    // What it'll be when the CPP is through with it:
    
    @property bool 1;
    
    // ^ That's also what will be in the error message that gets thrown by the compiler.
    #define doesn't take context into account at all. It doesn't even check that something is surrounded by spaces. It's simply a find and replace operation across your entire source code.

    Also, I suggest using switch/case instead of if-elses for checking its value.
     
  3. Duncan C macrumors 6502a

    Duncan C

    Joined:
    Jan 21, 2008
    Location:
    Northern Virginia
    #3
    I agree that enum is the way to go.

    A plus with enums is that if you use a switch statement, the compiler knows what the possible values are, and will warn you if you don't handle all the possible values.

    Also the debugger knows how to display the value of variables that are declared as an enum type.

    My business partner swears by #defines, and I can't break him of it. It drives me crazy.
     
  4. charmofmaking thread starter macrumors member

    Joined:
    Feb 2, 2013
    #4
    Thanks so much, I will go with typedef enum.

    The tip about switch statements is a good one. Just read the very last chapter in the Big Nerd Ranch book and guess what - switch statements :D

    #Define appears in some of the Game Center samples I've used and I didn't really grasps that it just gets pasted into your code before it is executed - thanks for the clarity and the warning. Not too bad I guess for a @"com.something.app.leaderboard" string but could get real messy real quick with numerical values!


     
  5. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #5
    Yeah, #defines work well for "key" values (and other frequently-used but perhaps-lengthy strings) but not so much for enumerated values (hence "enum").
     
  6. ArtOfWarfare macrumors 604

    ArtOfWarfare

    Joined:
    Nov 26, 2007
    #6
    Can you actually come up with a place that a #define should be used, outside of maybe having something to do with build settings?
     
  7. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #7
    I use them pretty frequently for NSUserDefaults keys. For example:
    Code:
    #define kDefaultDifficulty @"defaultDifficulty"
    Then, when I want to look the value up:
    Code:
    NSString *defaultDifficulty = [[NSUserDefaults standardUserDefaults] objectForKey:kDefaultDifficulty];
    This way, I eliminate the risk of getting the string (@"defaultDifficulty") typed wrong when I use it, and if I misspell the define "name", I get a compile error.
     
  8. ArtOfWarfare macrumors 604

    ArtOfWarfare

    Joined:
    Nov 26, 2007
    #8
    I always use const NSStrings. The same code ends up being generated, you have the same protection from misspelling, and you don't run the risk of having the evilness of #define rear its ugly head.
     
  9. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #9
    I've just always followed Apple's lead here. I have seen the #define approach used in a number of their sample apps, but I have not yet seen them use 'const NSString' for the same effect. I can see how it could potentially cause some "evilness" but I've never run into it. Also, I don't believe the same code is generated (#define is handled pre compile; const is handled during compile), but I'm willing to be proven wrong.
     
  10. chown33, Jun 5, 2013
    Last edited: Jun 5, 2013

    chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #10
    There are examples of const NSStrings in various headers and frameworks.

    A simple example is:
    http://developer.apple.com/library/...cessibility_Protocol/Reference/Reference.html

    Find any of the NSAccessibility strings. Search for NSAccessibilityRole and you'll find many of them.

    Then look at the header and see how it's declared:
    Code:
    APPKIT_EXTERN NSString *const NSAccessibilityRoleAttribute;
    
    where APPKIT_EXTERN is a #defined macro whose definition depends on what the source language is: C or C++. (Example is from the 10.4 SDK because it's what's on the machine I'm using right now.)


    The advantage of an extern NSString* const is there must be a defining declaration (e.g. in a non-header file), and because it's declared as const, it must have an initializer expression. If a defining declaration is absent, the linker will complain. If the defining declaration lacks an initializer, the compiler will complain. If a source file refers to it without a declaration (i.e. failure to import the header), the compiler also complains.

    There was a time when NSString literals were not common between compilation units. So a literal @"Something" in one compilation unit would be a different object than a literal @"Something" in a different compilation unit. This would lead to excessive string literals. I don't recall if recent Obj-C compilers (or linkers) remedies this, but there's an easy test program, which I leave as an exercise for the interested reader.


    Also note that this:
    Code:
    extern NSString* const someStr;
    
    differs from this:
    Code:
    extern const NSString* someStr;
    
    The first is a const pointer to an NSString, i.e. the someStr pointer itself is immutable, with type pointer-to-NSString. The second is a pointer to a const NSString; the someStr pointer is mutable and the referenced object is const.

    The use of 'const' with pointer types can be a dangerous trap for the unwary.
     
  11. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #11
    Thanks, chown33. Seems interesting that their frameworks would use this approach but not any the sample code for iOS (or at least, any of the sample code I've looked at).

    Anyways, thanks for the explanation. I understand it better now.
     
  12. ArtOfWarfare macrumors 604

    ArtOfWarfare

    Joined:
    Nov 26, 2007
    #12
    Oh dear, I think I've been getting these backwards... I've been using const NSString * someString = @"someString";

    I haven't been using extern... I don't understand what the value of it is, yet.
     
  13. ScottishCaptain macrumors 6502a

    Joined:
    Oct 4, 2008
    #13
    Several things:

    1) Enums are designed for precisely what op wants. A lot of people override the first variable to be zero though:

    typedef enum {
    isStory = 0,
    isMenu,
    isChallenge,
    } gameMode;

    The reason for this is because there really isn't anything that says the enumeration will start at 0. It is kind of assumed and expected, just about every compiler out there does it, but I find that adding the "= 0" removes any chance for ambiguity. You now know that isStory is equivalent to 0, isMenu is now equivalent to 1, and isChallenge is equivalent to 2.

    2) Don't #define NSStrings. Ever. This is really, really bad code practice.

    The reason for this is because #define is a preprocessor statement. It is expanded BEFORE the code is compiled. Therefore, if you have the following:

    #define kMyString @"MyKeyValue"

    ...

    [someObject1 someMethod1:kMyString];
    [someObject2 someMethod2:kMyString];
    [someObject3 someMethod3:kMyString];

    After being fed through the C preprocessor, you get this:

    [someObject1 someMethod1:mad:"MyKeyValue"];
    [someObject2 someMethod2:mad:"MyKeyValue"];
    [someObject3 someMethod3:mad:"MyKeyValue"];

    Now you've got the string @"MyKeyValue" included in your program three times. It is up to the compiler to be smart enough to optimize this away and recognize that these can all be replaced with a single instance of @"MyKeyValue". CLANG happens to be smart enough to do this, but I still think it's bad code practice.

    If you need to define static strings, you should really follow chown33's post above this one. The advantage of using NSString * const kMyStaticString = @"SomeStupidString"; is that the compiler will automatically know that all instances of kMyStaticString should point to the same location in memory, because it's defined as a constant pointer to an NSString object.

    Basically, if your compiler happens to be an idiot, using #define for strings can mean that the string itself is literally included in your binary however many times you use it in and around your code (which would be hundreds or thousands). If your compiler is an idiot and you're using the constant NSString pointer, then there's no ambiguity here at all- you don't need to rely on optimizations the compiler MIGHT employ. You are pointing to a single NSString object in memory and that is that, no matter how many times you reference it in your code the literal string value of that NSString will only be present in your application binary ONCE.

    -SC
     
  14. chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #14
    http://www.antlr.org/wiki/display/CS652/How+To+Read+C+Declarations

    Google search terms: read C declarations


    There used to be a C program that would take a C declaration and translate it to an English description. E.g.
    Code:
    int foo[5];
    
    would give something like "foo is an array of 5 ints." Another example:
    Code:
    int * bar[10];
    
    would give "bar is an array of 10 pointers to int.". (I hope I got those right, because I'm not actually using the program.)

    I don't recall the name of the program, or whether it still exists in some form. As I recall, the English for a pointer to a function was still somewhat incomprehensible, although it was accurate. "inscrutable is a pointer to a function with two parameters, int and pointer to char, returning type pointer to char". Aaand I'm gonna leave the C declaration for the interested reader.


    When you learn the use of it, you'll learn why it's useful.
     
  15. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #15
    What about something like?:
    Code:
    #define kSegmentedControlHeight 40.0
    (from the UICatalog sample code)

    Fine as it is?
     
  16. ScottishCaptain, Jun 5, 2013
    Last edited: Jun 5, 2013

    ScottishCaptain macrumors 6502a

    Joined:
    Oct 4, 2008
    #16
    Yeah, that's fine and very typical.

    It's only really NSStrings where you should be using the constant pointer version instead of a #define, if you're writing Objective C code and linking to the foundation libraries. That's the way Apple does it, and they do that for the reasons I outlined above.

    -SC
     
  17. ArtOfWarfare macrumors 604

    ArtOfWarfare

    Joined:
    Nov 26, 2007
    #17
    No. That should be

    Code:
    const CGFloat kSegmentedControlHeight = 40.0;
    #define is an all but obsolete relic of early versions of CPP that predates the const keyword from (I believe) C99 and C++85 (? I'm pretty sure it's been around since the very first version of C++...)

    You shouldn't be using it in new code EXCEPT when you're using it in conjunction with CPP logic.
     
  18. Duncan C macrumors 6502a

    Duncan C

    Joined:
    Jan 21, 2008
    Location:
    Northern Virginia
    #18
    #define is C, not C++. It was in the original version of the C language, dating back to the 1960s.

    It's not obsolete by any means. Its still in widespread use.

    I share your dislike of it as a way of defining enumerated constants, but this is a basically a religious issue, and you need to accept that there are others who worship the #define god, where you distain that god.
     
  19. ArtOfWarfare, Jun 5, 2013
    Last edited: Jun 5, 2013

    ArtOfWarfare macrumors 604

    ArtOfWarfare

    Joined:
    Nov 26, 2007
    #19
    I never said #define was a part of C++, and it's not a part of C. It's a part of CPP.

    http://countminus1.wordpress.com/2013/01/04/what-does-cpp-stand-for/
     
  20. chown33, Jun 5, 2013
    Last edited: Jun 5, 2013

    chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #20
    AFAIK, the behavior of cpp is defined entirely by the C standards. There is no separate standard for cpp. Thus, when it comes to determining what is correct behavior for cpp, the C standard rules. For example, the behavior for stringizing or string tokenizing, two behaviors that were completely absent from the original cpp, is defined entirely by the C standards.

    So yes, cpp is a part of C, in the sense that it's impossible to have a compliant C compiler if one lacks a compliant pre-processor.

    And a separate pre-processor is not a requirement of any C standard. It's entirely possible to have a compiler that does pre-processing and compilation in a single integrated pass. It boils down to "acts as if", which is a behavioral definition, rather than dictating an implementation.

    EDIT
    It just occurred to me that sometimes people are confused by a file suffix, such as "foo.cpp".
    http://en.wikipedia.org/wiki/.cpp

    In olden days, a '+' could be problematic in certain contexts, such as being invalid in a filename (file-system dependent), or possibly in earlier versions of make or sccs. As a result, file-suffixes for building source files were exclusively alphanumeric characters, devoid of punctuation except the dot delimiting basename from suffix.

    Before C++ existed, .cpp files were typically destined to be processed only by the 'cpp' command (which was a distinct command all the way back), generally as a phase before additional processing of some kind. For example, I distinctly remember working with both Fortran and Pascal compilers that lacked any macros, conditional compile, or include capability, for which 'cpp' was perfectly suited.

    These days, default build rules are more likely to treat a .cpp file as C++, so specific rules in the makefile are needed.
     

Share This Page