Developer.com Logo Click here to support our advertisers
Click here to support our advertisers
SHOPPING
JOB BANK
CLASSIFIEDS
DIRECTORIES
REFERENCE
Online Library
LEARNING CENTER
JOURNAL
NEWS CENTRAL
DOWNLOADS
COMMUNITY
CALENDAR
ABOUT US

Journal:

Get the weekly email highlights from the most popular journal for developers!
Current issue
developer.com
developerdirect.com
htmlgoodies.com
javagoodies.com
jars.com
intranetjournal.com
javascripts.com

All Categories : Linux

developer.com - Reference
Developer.com Logo Click here to support our advertisers
Click here to support our advertisers
SHOPPING
JOB BANK
CLASSIFIEDS
DIRECTORIES
REFERENCE
Online Library
LEARNING CENTER
JOURNAL
NEWS CENTRAL
DOWNLOADS
COMMUNITY
CALENDAR
ABOUT US

Journal:

Get the weekly email highlights from the most popular journal for developers!
Current issue
developer.com
developerdirect.com
htmlgoodies.com
javagoodies.com
jars.com
intranetjournal.com
javascripts.com

Linux


- 28 -

Programming in C++

by Rick McMullin

IN THIS CHAPTER

  • What Is C++?

  • Debugging C++ Applications 517

n GNU C++ Class Libraries 520 Chapter 27, "Programming in C," introduced you to the C programming environment and C programming tools that come with Linux. This chapter describes the same kinds of information for C++. This chapter covers the following topics:

  • What C++ is

  • Why to use C++

  • The GNU C++ compiler

  • Debugging C++ applications

In addition to these topics, this chapter also looks at some of the C++ programming tools and class libraries that are included on the Linux CD-ROM.

What Is C++?

C++ is an object-oriented extension to the C programming language. It was developed at Bell Labs in the early 1980s and is quickly becoming the language of choice in the computer industry. Dozens of C++ compilers are available on the market today. The most common of these for PC-based systems are Borland C++, Microsoft's Visual C++, Zortech C++, and Watcom C++. These compilers compile MS DOS and MS Windows applications, and some of them compile code to run on OS/2 and Windows NT as well. In addition to the number of C++ compilers that are available on DOS-based machines, a great number are also based on other hardware architectures.

Most UNIX systems have C++ compilers available from the system vendor. Linux also comes with a C++ compiler. This is the GNU C++ compiler. The GNU C++ compiler is very closely related to the GNU C compiler (GCC). In fact, since Release 2.0 of GCC, the GNU C++ compiler has been integrated with GCC. Previous to Release 2.0 of GCC, the GNU C++ compiler was a separate program known as g++. One of the major enhancements in Release 2.0 of GCC was merging these two compilers.

GCC now incorporates a C compiler, a C++ compiler, and an Objective C compiler. You will still find the g++ executable on your system, but it is now a script file that calls GCC with all the standard C++ options.

Why C++?

C++ and object-oriented programming (OOP) did not just happen. There were many funda- mental reasons for the shift from structured programming to OOP. In the early days of computer programming, back when PDP-8s still roamed the earth in great numbers, there was a shift from machine language coding to assembler language coding. This was done because the computers of the day were a little more powerful than their predecessors. Programmers wanted to make their lives easier by moving some of the burden of programming onto the computer.

As the years went by and computers got even more powerful, new, higher-level languages started to appear. Examples of these languages are FORTRAN, COBOL, Pascal, and C. With these languages came a programming methodology known as structured programming. Structured programming helped to simplify the systems being designed by enabling programmers to break the problem into small pieces and then implement these pieces as functions or procedures in whatever language was being used.

The structured programming approach worked well for small to medium-sized software applications, but it started to fall apart as systems reached a certain size. OOP tried to solve some of the problems that structured programming was causing. It did this by extending some of the structured programming concepts and by introducing some of its own.

The main concepts that OOP focuses on are the following:

  • Data encapsulation

  • Inheritance

  • Polymorphism

Data Encapsulation In structured programming, problems often arose where there was a data structure that was common to several different pieces of code. One piece of code could access that data without the other piece of code being aware that anything was happening.

Data encapsulation is a process of grouping common data together, storing it into a data type, and providing a consistent interface to that data. This ensures that no one can access that data without going through the user interface that has been defined for that data.

The biggest benefit that this kind of mechanism provides is that it protects code outside the code that is directly managing this data from being affected if the structure of the data changes. This greatly reduces the complexity of large software systems.

C++ implements data encapsulation through the use of classes. Inheritance Inheritance is a form of code reuse in which you can inherit or use the data and behavior of other pieces of code. Inheritance is typically used only when a piece of software logically has many of the same characteristics as another piece of software, such as when one object is a specialization of another object.

Inheritance is implemented in C++ by allowing objects to be subclassed by other objects. Polymorphism Polymorphism occurs when a language allows you to define functions that perform different operations on objects depending on their type. The true power of this lies in the fact that you can send a message to a base class and that message can be passed down to each of its subclasses and mean different things to each of them.

Polymorphism is implemented in C++ using virtual functions.

Classes of Objects and Methods

In C++, classes can be thought of as C structures that contain not only the data fields but also operations that can be performed on those data fields. A simple example of this concept is a geometric shape. A geometric shape can be many things, such as a rectangle, a triangle, or a circle. All geometric shapes have certain attributes in common, including area and volume. You could define a structure in C called shape in the following way:

struct shape{

        float area;

        float volume;

}

If you added some common behavior to this structure, you would have the equivalent of a C++ class. This would be written as follows:

class shape {

public:

               float area;

               float volume;

               float calc_area();

               float calc_volume():

};

You have now defined a C++ class. The calc_area and calc_volume items are known as methods of the class (instead of functions, as in C). If you were to define a variable that was of type shape as

shape circle;

you would have created a circle object. An object is an instance of a class, or a variable that is defined to be of the type of a class.

GCC Options

This section describes some of the GCC command-line options that are most commonly used. I will first talk about some of the options that can be used both with C and C++ and then talk about C++ specific options. Any of the compiler options that you use with C you can use with C++ as well, but some of them may not make any sense in the context of a C++ compile. If you specify options that don't make sense, the compiler just ignores them.


NOTE: When you are compiling C++ programs, it is easiest to use the g++ script. This sets all the default C++ options so you don't have to.

A great number of compiler options can be passed to GCC. Many of these options are specific to a certain hardware platform or are for making fine-tuning adjustments to the code that is produced. You will probably never use any of these kinds of options. The options covered in this chapter are those that you will use on a regular basis.

Many of the GCC options consist of more than one character. For this reason, you must specify each option with its own hyphen and not group options after a single hyphen as you can with most Linux commands.

When you compile a program using GCC without any command-line options, it creates an executable file (assuming that the compile was successful) and calls it a.out. For example, the following command would create a file named a.out in the current directory:

gcc test.C

To specify a name other than a.out for the executable file, you can use the -o compiler option. For example, to compile a C++ program file named count.C (the capital C is used to show C++ code, as opposed to a small c for C code) into an executable file named count, you would type the following command:

gcc -o count count.C


NOTE: When you are using the -o option, the executable filename must occur directly after the -o on the command line.

Other compiler options allow you to specify how far you want the compile to proceed. The -c option tells GCC to compile the code into object code and skip the assembly and linking stages of the compile. This option is used quite often because it makes the compilation of multifile C++ programs faster and easier to manage. Object code files created by GCC have an .o extension by default.

The -S compiler option tells GCC to stop the compile after it has generated the assembler files for the C code. Assembler files generated by GCC have an .s extension by default. The -E option instructs the compiler to perform only the preprocessing compiler stage on the input files. When this option is used, the output from the preprocessor is sent to the standard output rather than being stored in a file.

Debugging and Profiling Options

GCC supports several debugging and profiling options. Of these options, the two that you are most likely to use for C++ programs are the -gstabs+ option and the -pg option.

The -gstabs+ option tells GCC to produce stabs format debugging information that the GNU debugger (gdb) can use to help you debug your program. For more information on debugging your C++ programs, refer to the "Debugging C++ Applications" section on the next page.

The -pg option tells GCC to add extra code to your program that will, when executed, generate profile information that can be used by the gprof program to display timing information about your program. For additional information on gprof, refer to the "gprof" section in Chapter 27.

GCC C++ Specific Options

The GCC options that control how a C++ program is compiled are listed in Table 28.1.

Table 28.1. GCC options.
Option Meaning
-fall-virtual Treats all possible member functions as virtual. This applies to all functions except for constructor functions and new or deleted member functions.
-fdollars-in-identifiers Accepts $ in identifiers. You can also prohibit the use of $ in identifiers by using the -fno-dollars-in-identifiers option.
-felide-constructors Tells the compiler to leave out constructors whenever possible.
-fenum-int-equiv Permits implicit conversion of int to enumeration types.
-fexternal-templates Produces smaller code for template declarations. This is done by having the compiler generate only a single copy of each template function where it is defined.
-fmemorize-lookups Uses heuristics to compile faster. These heuristics are not enabled by default because they are effective only for certain input files.
-fno-strict-prototype Treats a function declaration with no arguments the same way that C would treat it. This means that the compiler treats a function prototype that has no arguments as a function that will accept an unknown number of arguments.
-fno-null-objects Assumes that objects reached through references are not null.
-fsave-memorized Same as -fmemorize-lookups.
-fthis-is-variable Permits assignment to "this."
-nostdinc++ Does not search for header files in the standard directories specific to C++.
-traditional This option has the same effect as -fthis-is-variable.
-fno-default-inline Does not assume that functions defined within a class scope are inline functions.
-wenum-clash Warns about conversion between different enumeration types.
-woverloaded-virtual Warns when derived class function declaration may be an error in defining a virtual function. When you define a virtual function in a derived class, it must have the same signature as the function in the base class. This option tells the compiler to warn you if you have defined a function that has the same name and a different signature as a function that is defined in one of the base classes.
-wtemplate-debugging If you are using templates, this option warns you if debugging is not yet available.
+eN Controls how virtual function definitions are used.
-gstabs+ Tells the compiler to generate debugging information in stabs format, using GNU extensions understood only by the GNU debugger. The extra information produced by this option is necessary to ensure that gdb handles C++ programs properly.

Debugging C++ Applications

A very important part of developing C++ programs is being able to debug them efficiently. The GNU debug application that was introduced in Chapter 27 can also be used to debug C++ applications. This section describes some of the differences between debugging C applications and debugging C++ applications.

The basic gdb commands that were introduced in Chapter 27 are listed again for your convenience in Table 28.2.

Table 28.2. Basic gdb commands.
Command Description
file Loads the executable file that is to be debugged.
kill Terminates the program you are currently debugging.
list Lists sections of the source code used to generate the executable file.
next Advances one line of source code in the current function, without stepping into other functions.
step Advances one line of source code in the current function, and does step into other functions.
run Executes the program that is currently being debugged.
quit Terminates gdb.
watch Enables you to examine the value of a program variable whenever the value changes.
break Sets a breakpoint in the code; this causes the execution of the program to be suspended whenever this point is reached.
make Enables you to remake the executable program without quitting gdb or using another window.
shell Enables you to execute UNIX shell commands without leaving gdb.

From the programmer's perspective, you have more details to be aware of when debugging C++ code than when you are debugging C code. This is because of the C++ features such as virtual functions and exception handling. gdb has added features to support debugging both of these C++ specific features.

Debugging Virtual Functions

As described in the "Polymorphism" section, earlier in this chapter, virtual functions are C++'s way of implementing polymorphism. This means that there may be more than one function in a program with the same name. The only way to tell these functions apart is by their signatures. The signature of a function is composed of the types of all the arguments to the function. For example, a function with the prototype

void func(int, real);

has a signature of int,real.

You can see how this could cause the gdb a few problems. For example, if you had defined a class that had a virtual function called calculate, and two objects with different definitions for this function were created, how would you set a breakpoint to trigger on this function? You set breakpoints in C by specifying the function name as an argument to the gdb break command, as follows:

(gdb) break calculate

This does not work in the case of a virtual function because the debugger would not be able to tell which calculate you wanted the breakpoint to be set on. gdb was extended in a few ways so that it could handle virtual functions. The first way to solve the problem is to enter the function name by specifying its prototype as well. This would be done in the following way:

break `calculate (float)'

This would give gdb enough information to determine which function the breakpoint was meant for. A second solution that gdb supports is using a breakpoint menu. Breakpoint menus allow you to specify the function name of a function. If there is more than one function definition for that function, it gives you a menu of choices. The first choice in the menu is to abort the break command. The second choice is to set a breakpoint on all the functions that the break command matches. The remaining choices correspond to each function that matches the break command. The following code shows an example of a breakpoint menu:

 (gdb) break shape::calculate

[0] cancel

[1] all

[2] file: shapes.C: line number: 153

[3] file: shapes.C: line number: 207

[4] file: shapes.C: line number: 247

> 2 3

Breakpoint 1 at 0xb234: file shapes.C, line 153

Breakpoint 2 at 0xa435: file shapes.C, line 207

Multiple breakpoints were set

Use the "delete" command to delete unwanted breakpoints

(gdb)

Debugging Exception Handlers

Exceptions are errors that occur within your program. Exception handlers are pieces of code that are written to handle errors and potential errors. For example, if you were writing a C program and calling the malloc function to get a block of memory, you would typically check malloc's return code to make sure the memory allocation was successful. If C supported exception handling, you could specify a function that would receive or catch exceptions, and the malloc function would send or throw an exception to your function if one occurred.

The gdb added two new commands to support C++ exception handling: the catch command and the catch info command. The catch command is used to set a breakpoint in active exception handlers. The syntax of this command is as follows:

catch exceptions

exceptions is a list of the exceptions to catch.

The catch info command is used to display all the active exception handlers.

Summary of gdb C++ Specific Commands

In addition to the gdb commands that have been added to support some of the new language features contained in C++, there are also some new set and show options. These options are listed in Table 28.3.

Table 28.3. gdb's C++ set and show options.
Command Description
set print demangle Prints C++ names in their source form rather than in the encoded or mangled form that is passed to the assembler.
show print demangle Shows whether print demangle is on or off.
set demangle-style Sets the style of demangled output. The options are auto, gnu, lucid, and arm.
show demangle-style Shows which demangle style is being used.
set print object When displaying a pointer to an object, identifies the actual type of the object.
show print object Shows whether print object is turned on or off.
set print vtbl Pretty prints C++ virtual function tables.
show print vtbl Shows whether print vtbl is turned on or off.

GNU C++ Class Libraries

GNU C++ comes packaged with an extensive class library. A class library is a reusable set of classes that can be used to perform a specified set of functions. Some typical examples of class libraries are class libraries that handle database access, class libraries that handle graphical user interface programming, and class libraries that implement data structures.

Examples of graphical user interface class libraries include the Microsoft Foundation Classes and Borland's Object Windows Library, both of which are class libraries that are used for developing Windows applications.

This section introduces several of the features that are offered by the GNU C++ class library.

Streams

The GNU iostream library, called libio, implements GNU C++'s standard input and output facilities. This library is similar to the I/O libraries that are supplied by other C++ compilers. The main parts of the iostream library are the input, output, and error streams. These correspond to the standard input, output, and error streams that are found in C and are called cin, cout, and cerr, respectively. The streams can be written to and read from using the << operator for output and the >> operator for input.

The following program uses the iostream library to perform its input and output:

#include <iostream.h>

int maim ()

{      

        char name[10];

        cout << "Please enter your name.\n";

        cin >> name;

        cout << "Hello " << name << " how is it going?\n";

}

Strings

The GNU string class extends GNU C++'s string manipulation capabilities. The string class essentially replaces the character array definitions that existed in C and all the string functions that go along with the character arrays.

The string class adds UNIX shell type string operators to the C++ language, as well as a large number of additional operators. Table 28.4 lists many of the operators that are available with the string class.

Table 28.4. String class operators.
Operator Meaning
str1 == str2 Returns TRUE if str1 is equal to str2
str1 != str2 Returns TRUE if str1 is not equal to str2
str1 < str2 Returns TRUE if str1 is less than str2
str1 <= str2 Returns TRUE if str1 is less than or equal to str2
str1 > str2 Returns TRUE if str1 is greater than str2
str1 >= str2 Returns TRUE if str1 is greater than or equal to str2
compare(str1,str2) Compares str1 to str2 without considering the case of the
characters
str3 = str1 + str2 Stores the result of str1 concatenated with str2 into str3


A number of other operators are available in the string class for performing different types of string comparisons, concatenations, and substring extraction and manipulation.

Random Numbers

Classes are provided in the GCC C++ class library that allow you to generate several different kinds of random numbers. The classes used to generate these numbers are the Random class and the RNG class.

Data Collection

The class library provides two different classes that perform data collection and analysis functions. The two classes are SampleStatistic and SampleHistogram. The SampleStatistic class provides a way of collecting samples and also provides numerous statistical functions that can perform calculations on the collected data. Some of the calculations that can be performed are mean, variance, standard deviation, minimum, and maximum.

The SampleHistogram class is derived from the SampleStatistic class and supports the collection and display of samples in bucketed intervals.

Linked Lists

The GNU C++ library supports two kinds of linked lists: single linked lists, implemented by the SLList class, and doubly linked lists, implemented by the DLList class. Both of these types of lists support all the standard linked list operations. A summary of the operations that these classes support is shown in Table 28.5.

Table 28.5. List operators.
Operator Description
list.empty() Returns TRUE if list is empty
list.length() Returns the number of elements in list
list.prepend(a) Places a at the front of list
list.append(a) Places a at the end of list
list.join(list2) Appends list2 to list, destroying list2 in the process
a = list.front() Returns a pointer to the element that is stored at the head of the list
a = list.rear() Returns a pointer to the element that is stored at the end of the list
a = list.remove_front() Deletes and returns the element that is stored at the front of the list
list.del_front() Deletes the first element without returning it
list.clear() Deletes all items from list
list.ins_after(i, a) Inserts a after position i in the list
list.del_after(i) Deletes the element following position i in the list

Doubly linked lists also support the operations listed in Table 28.6.

Table 28.6. Doubly linked list operators.
Operator Description
a = list.remove_rear() Deletes and returns the element stored at the end of the list
list.del_real() Deletes the last element, without returning it
list.ins_before(i, a) Inserts a before position i in the list
list.del(i, dir) Deletes the element at the current position and then moves forward one position if dir is positive and backward one position if dir is 0 or negative

Plex Classes

Plex classes are classes that behave like arrays but are much more powerful. Plex classes have the following properties:

  • They have arbitrary upper and lower index bounds.

  • They can dynamically expand in both the lower and upper bound directions.

  • Elements may be accessed by indices. Unlike typical arrays, bounds checking is performed at runtime.

  • Only elements that have been specifically initialized or added can be accessed.

Four different types of Plexes are defined: the FPlex, the XPlex, the RPlex, and the MPlex. The FPlex is a Plex that can grow or shrink only within declared bounds. An XPlex can dynamically grow in any direction without any restrictions. An RPlex is almost identical to an XPlex, but it has better indexing capabilities. Finally, the MPlex is the same as an RPlex except that it allows elements to be logically deleted and restored.

Table 28.7 lists some of the operations that are valid on all four of the Plexes.

Table 28.7. Operations defined for Plexes.
Operation Description
Plex b(a) Assigns a copy of Plex a to Plex b
b = a Copies Plex a into b
a.length() Returns the number of elements in a
a.empty() Returns TRUE if a has no elements
a.full() Returns TRUE if a is full
a.clear() Removes all the elements from a
a.append Appends Plex b to the high part of a
a.prepend Prepends Plex b to the low part of a
a.fill(z) Sets all elements of a equal to z
a.valid(i) Returns TRUE if i is a valid index into a
a.low_element() Returns a pointer to the element in the lowest position in a
a.high_element() Returns a pointer to the element in the highest position in a

Plexes are a very useful class on which many of the other classes in the GNU C++ class library are based. Some of the Stack, Queue, and Linked list types are built on top of the Plex class.

Stacks

The Stack class implements the standard version of a last-in-first-out (LIFO) stack. Three different implementations of stacks are offered by the GNU C++ class library: the VStack, the XPStack, and the SLStack. The VStack is a fixed-size stack, meaning that you must specify an upper bound on the size of the stack when you first create it. The XPStack and the SLStack are both dynamically sized stacks that are implemented in a slightly different way.

Table 28.8 lists the operations that can be performed on the Stack classes.

Table 28.8. Stack class operators.
Operator Description
Stack st Declares st to be a stack
Stack st(sz) Declares st to be a stack of size sz
st.empty() Returns TRUE if stack is empty
st.full() Returns TRUE if stack is full
st.length() Returns the number of elements in stack
st.push(x) Puts element x onto the top of the stack
x = st.pop() Removes and returns the top element from the stack
st.top() Returns a pointer to the top element in the stack
st.del_top() Deletes the top element from the stack without returning it
st.clear() Deletes all elements from stack

Queues

The Queue class implements a standard version of a first-in-first-out (FIFO) queue. Three different kinds of queue are provided by the GNU C++ class library: the VQueue, the XPQueue, and the SLQueue. The VQueue is a fixed-size queue, so you must specify an upper bound on the size of this kind of queue when you first create it. The XPQueue and the SLQueue are both dynamically sized queues, so no upper bound is required. The operations supported by the Queue classes are listed in Table 28.9.

Table 28.9. Queue class operators.
Operator Description
Queue q Declares q to be a queue
Queue q(sz) Declares q to be a queue of size sz
q.empty() Returns TRUE if q is empty
q.full() Returns TRUE if q is full
q.length() Returns the number of elements in q
q.enq(x) Adds the x element to q
x = q.deq() Removes and returns an element from q
q.front() Returns a pointer to the front of q
q.del_front() Removes an element from q and does not return the result
q.clear Removes all elements from the queue

In addition to the normal kind of queue that is discussed in this section, the GNU C++ class library also supports double-ended queues and priority queues. Both of these types of queues have similar behavior to the regular queue. The double-ended queue adds operators for returning a pointer to the rear of the queue and deleting elements from the rear of the queue. The priority queues are arranged so that a user has fast access to the least element in the queue. They support additional operators that allow for searching for elements in the queue.

Sets

The Set class is used to store groups of information. The only restriction on this information is that no duplicate elements are allowed. The class library supports several different implementations of sets. All of the implementations support the same operators. These operators are shown in Table 28.10.

Table 28.10. Set operators.
Operator Description
Set s Declares a set named s that is initially empty
Set s(sz) Declares a set named s that is initially empty and has a set maximum size of sz
s.empty() Returns TRUE if s is empty
s.length() Returns the number of elements in s
i = s.add(z) Adds z to s, returning its index value
s.del(z) Deletes z from s
s.clear() Removes all elements from s
s.contains(z) Returns TRUE if z is in s
s.(i) Returns a pointer to the element indexed by i
i = a.first() Returns the index of the first item in the set
s.next(i) Makes i equal to the index of the next element in s
i = s.seek(z) Sets i to the index of z if z is in s, and 0 otherwise
set1 == set2 Returns TRUE if set1 contains all the same elements as set2
set1 != set2 Returns TRUE if set1 does not contain all the same elements as set2
set1 <= set2 Returns TRUE if set1 is a subset of set2
set1 |= set2 Adds all elements of set2 to set1
set1 -= set2 Deletes all the elements that are contained in set2 from set1
set1 &= set2 Deletes all elements from set1 that occur in set1 and not in set2

The class library contains another class that is similar to sets. This class is known as the bag. A bag is a group of elements that can be in any order (just as is the case with sets) but in which there can also be duplicates. Bags use all the operators that sets use except for the ==, !=, |=, <=, |=, -=, and &= operators. In addition, bags add two new operators for dealing with elements that are in the bag more than once. These new operators are shown in Table 28.11.

Table 28.11. Additional operators for bags.
Operator Description
b.remove(z) Deletes all occurrences of z from b
b.nof(z) Returns the number of occurrences of z that are in b

Many other classes available in the GNU C++ class library provide functions other than those listed here. In addition to what comes with the compiler, many other freely available class libraries can be useful as well.

Summary

C++ offers many advantages over C. Some of these advantages come from the concepts of object-oriented programming, and others come from the highly flexible class libraries that are available to C++ programmers. This chapter gave a brief introduction to object-oriented programming and also talked about the C++ features that exist in the GNU C compiler and the GNU debugger.

One problem that has existed with C++ for quite some time is the lack of freely available C++ development tools. You may notice that the number of free tools available for C++ programming is much smaller than the number available for C, but the tide is turning. As more and more people choose C++ over C, the number of tools and class libraries available keeps increasing. The tool support has reached the stage where learning C++ in the Linux environment is something that you can enjoy rather than avoid. Ruler image
Contact
[email protected] with questions or comments.
Copyright 1998 EarthWeb Inc., All rights reserved.
PLEASE READ THE ACCEPTABLE USAGE STATEMENT.
Copyright 1998 Macmillan Computer Publishing. All rights reserved.

Click here for more info

Click here for more info